题目描述
难度分:1400
输入T(≤104)表示T组数据。所有数据的n之和≤2×105。
每组数据输入n(3≤n≤2×105)和三个长为n的数组a、b、c,元素范围[1,106]。保证三个数组的元素和都等于tot。
你需要找到三个不相交区间[l1,r1], [l2,r2], [l3,r3],满足
- a的[l1,r1]的子数组和≥ceil(tot3)。
- b的[l2,r2]的子数组和≥ceil(tot3)。
- c的[l3,r3]的子数组和≥ceil(tot3)。
输出任意满足要求的l1,r1,l2,r2,l3,r3(下标从1开始)。如果无解,输出−1。注意三个区间没有位置顺序,例如[l1,r1]不一定在最左边。
输入样例
10
5
5 1 1 1 1
1 1 5 1 1
1 1 1 1 5
6
1 2 3 4 5 6
5 6 1 2 3 4
3 4 5 6 1 2
4
4 4 4 4
4 4 4 4
4 4 4 4
5
5 10 5 2 10
9 6 9 7 1
10 7 10 2 3
3
4 5 2
6 1 4
1 8 2
3
10 4 10
8 7 9
10 4 10
7
57113 65383 19795 53580 74452 3879 23255
12917 16782 89147 93107 27365 15044 43095
33518 63581 33565 34112 46774 44151 41756
6
6 3 1 8 7 1
10 2 6 2 2 4
10 9 2 1 2 2
5
5 5 4 5 5
1 6 3 8 6
2 4 1 9 8
10
1 1 1 1 1001 1 1 1001 1 1
1 1 1 1 1 1 2001 1 1 1
1 1 1 1 1 1001 1 1 1 1001
输出样例
1 1 2 3 4 5
5 6 1 2 3 4
-1
-1
1 1 3 3 2 2
-1
1 2 3 4 5 7
3 6 1 1 2 2
1 2 3 4 5 5
1 5 6 7 8 10
算法
前后缀分解
这种三元组问题一般来说很容易想到前后缀分解,预处理出前后缀信息,然后枚举中间那一段进行统计和判断。但是本题的三个区间没有顺序要求,这就使得情况比较多,写起来很繁琐。
先把a、b、c三个数组处理成前缀和数组,然后遍历数组,枚举子数组的右端点r,对于以r结尾的子数组,由于数组中的元素都是正数,因此前缀和是单调递增的,具有单调性。对于给定的r,二分找到最近的左端点l,确保子数组[l,r]的累加和≥ceil(tot3),令pre[r]=l。同理,遍历子数组的左端点l,二分找到最近的右端点r,确保子数组[l,r]的累加和≥ceil(tot3),令suf[l]=r。还需要预处理出来两个数组left和right,left[i]表示前缀[1,i]中是否有符合题意的子数组,right[i]表示后缀[i,n]中是否有符合题意的子数组。
以a的区间在中间,b的区间在左边,c的区间在右边为例进行说明。a、b、c三个数组预处理出来的信息为pre1、suf1、left1、right1、pre2、suf2、left2、right2、pre3、suf3、left3、right3。枚举a的左端点l,只要满足r=suf1[l]≠−1有解,就检查left2[l−1]和right3[r+1]是否为真,为真就说明[l,r]的左右两边都存在符合条件的子数组,遍历一下pre2和suf3,把b和c的子数组找出来就行。
一共存在以下6种情况,从左往右分别是取出区间对应的数组。每种情况都要做以上的检查,有解就保留一个解,如果一个解都没找到就输出−1:
- bac
- cab
- abc
- cba
- acb
- bca
复杂度分析
时间复杂度
对a、b、c三个数组,预处理出pre和suf的时间复杂度是O(nlog2)(遍历一个端点+二分查找另一个端点)。预处理出left、right的时间复杂度是线性的,为O(n)。最后穷举6种情况,通过前后缀分解找到答案,时间复杂度也为O(n)。
综上,整个算法的时间复杂度为O(nlog2n)。
空间复杂度
对于a、b、c三个数组,都开辟了几个线性空间的辅助数组pre、suf、left、right。因此整个算法的额外空间复杂度可以看做是O(n),只是常数比较大。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 200010;
int T, n, pre1[N], suf1[N], pre2[N], suf2[N], pre3[N], suf3[N];
LL a[N], b[N], c[N];
bool l1[N], r1[N], l2[N], r2[N], l3[N], r3[N];
void solve(LL v[], int pre[], int suf[], bool left[], bool right[]) {
for(int i = 1; i <= n; i++) {
int l = i, r = n, index = -1;
while(l <= r) {
int mid = l + r >> 1;
if(v[mid] - v[i - 1] >= (v[n] + 2)/3) {
index = mid;
r = mid - 1;
}else {
l = mid + 1;
}
}
suf[i] = index;
l = 1, r = i, index = -1;
while(l <= r) {
int mid = l + r >> 1;
if(v[i] - v[mid - 1] >= (v[n] + 2)/3) {
index = mid;
l = mid + 1;
}else {
r = mid - 1;
}
}
pre[i] = index;
}
left[0] = false;
for(int i = 1; i <= n; i++) {
left[i] = left[i - 1] || pre[i] != -1;
}
right[n + 1] = false;
for(int i = n; i >= 1; i--) {
right[i] = right[i + 1] || suf[i] != -1;
}
}
int main() {
scanf("%d", &T);
while(T--) {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
a[i] += a[i - 1];
pre1[i] = suf1[i] = pre2[i] = suf2[i] = pre3[i] = suf3[i] = -1;
}
for(int i = 1; i <= n; i++) {
scanf("%lld", &b[i]);
b[i] += b[i - 1];
}
for(int i = 1; i <= n; i++) {
scanf("%lld", &c[i]);
c[i] += c[i - 1];
}
solve(a, pre1, suf1, l1, r1);
solve(b, pre2, suf2, l2, r2);
solve(c, pre3, suf3, l3, r3);
bool ok = false;
vector<array<int, 2>> ans;
// a在中间
for(int l = 2; l < n && !ok; l++) {
int r = suf1[l];
if(r != -1) {
if(l3[l - 1] && r2[r + 1]) {
// c在左边,b在右边
ok = true;
ans.push_back({l, r});
for(int j = r + 1; j <= n; j++) {
if(suf2[j] != -1) {
ans.push_back({j, suf2[j]});
break;
}
}
for(int j = l - 1; j >= 1; j--) {
if(pre3[j] != -1) {
ans.push_back({pre3[j], j});
break;
}
}
}
if(!ok && l2[l - 1] && r3[r + 1]) {
// b在左边,c在右边
ok = true;
ans.push_back({l, r});
for(int j = l - 1; j >= 1; j--) {
if(pre2[j] != -1) {
ans.push_back({pre2[j], j});
break;
}
}
for(int j = r + 1; j <= n; j++) {
if(suf3[j] != -1) {
ans.push_back({j, suf3[j]});
break;
}
}
}
}
}
// b在中间
for(int l = 2; l < n && !ok; l++) {
int r = suf2[l];
if(r != -1) {
if(l1[l - 1] && r3[r + 1]) {
// a在左边,c在右边
ok = true;
for(int j = l - 1; j >= 1; j--) {
if(pre1[j] != -1) {
ans.push_back({pre1[j], j});
break;
}
}
ans.push_back({l, r});
for(int j = r + 1; j <= n; j++) {
if(suf3[j] != -1) {
ans.push_back({j, suf3[j]});
break;
}
}
}
if(!ok && l3[l - 1] && r1[r + 1]) {
// c在左边,a在右边
ok = true;
for(int j = r + 1; j <= n; j++) {
if(suf1[j] != -1) {
ans.push_back({j, suf1[j]});
break;
}
}
ans.push_back({l, r});
for(int j = l - 1; j >= 1; j--) {
if(pre3[j] != -1) {
ans.push_back({pre3[j], j});
break;
}
}
}
}
}
// c在中间
for(int l = 2; l < n && !ok; l++) {
int r = suf3[l];
if(r != -1) {
if(l1[l - 1] && r2[r + 1]) {
// a在左边,b在右边
ok = true;
for(int j = l - 1; j >= 1; j--) {
if(pre1[j] != -1) {
ans.push_back({pre1[j], j});
break;
}
}
for(int j = r + 1; j <= n; j++) {
if(suf2[j] != -1) {
ans.push_back({j, suf2[j]});
break;
}
}
ans.push_back({l, r});
}
if(!ok && l2[l - 1] && r1[r + 1]) {
// b在左边,a在右边
ok = true;
for(int j = r + 1; j <= n; j++) {
if(suf1[j] != -1) {
ans.push_back({j, suf1[j]});
break;
}
}
for(int j = l - 1; j >= 1; j--) {
if(pre2[j] != -1) {
ans.push_back({pre2[j], j});
break;
}
}
ans.push_back({l, r});
}
}
}
if(!ok) {
puts("-1");
}else {
for(auto&rng: ans) {
printf("%d %d ", rng[0], rng[1]);
}
puts("");
}
}
return 0;
}