传送门
解析:
我一开始还以为要用bitset维护一下出现了哪些和,但是后来发现发现bitset不好输出解。
思路:
其实我们为什么要维护出现过哪些和呢?
我们可以维护一下前缀和。
我们试着枚举元素总和比较小的的集合的每个前缀和。
然后我们在元素总和大的集合中调整指针,使得当前前缀刚好不小于我们刚才枚举的前缀和。
然后是关键的一步,我们计算前缀差,查询前面是否出现过同样的前缀差。
如果查询到一样的前缀差,那么当前的两个前缀分别减去之前前缀差相同的前缀就能得到我们要的元素和相同的区间。有可能前缀和从0开始,我们要从第0个位置开始枚举。
没有查询到就是无解。
那么,还记不记得标题?抽屉原理?
这关抽屉原理什么事?
那么你再想一想,我们枚举并查询了多少个前缀差?
从0开始,到n结束,一共
n
+
1
n+1
n+1种。
而实际上按照刚才的策略,有多少前缀差可能存在?
只有
n
n
n种,0到
n
−
1
n-1
n−1。为什么?
如果前缀差出现了n,根据我们的查询策略,在调整指针前的数必然不超过枚举到的前缀和-1,相等就会被查询到。
而这种情况说明我们加上了一个大小至少为
n
+
1
n+1
n+1的数,而这是不可能的。
那么 n + 1 n+1 n+1次查询更新,只有 n n n个位置,最后一次无论如何都必然查询到合适的答案。
所以不存在无解的情况。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const
#define _debug(x) cerr<<#x<<" : "<<x<<endl
inline
ll getint(){
re ll num;
re char c;
re bool f=0;
while(!isdigit(c=gc()))f^=c=='-';num=c^48;
while(isdigit(c=gc()))num=(num<<1)+(num<<3)+(c^48);
return f?-num:num;
}
inline
void outint(ll a){
static char ch[23];
if(a==0)pc('0');
if(a<0)a=-a,pc('-');
while(a)ch[++ch[0]]=a-a/10*10,a/=10;
while(ch[0])pc(ch[ch[0]--]^48);
}
cs int N=1000005;
bool vis[N];
int pos1[N],pos2[N];
bool flag;
int n;
ll sum1[N],sum2[N];
ll *p1=sum1,*p2=sum2;
int b1,b2,e1,e2;
signed main(){
n=getint();
for(int re i=1;i<=n;++i)sum1[i]=sum1[i-1]+getint();
for(int re i=1;i<=n;++i)sum2[i]=sum2[i-1]+getint();
if(sum1[n]<sum2[n])p1=sum2,p2=sum1,flag=true;
int j=0;
for(int re i=0;i<=n;++i){
while(p1[j]<p2[i])++j;
ll tmp=p1[j]-p2[i];
if(vis[tmp]){
b1=pos1[tmp];
b2=pos2[tmp];
e1=i;
e2=j;
break;
}
vis[tmp]=true;
pos1[tmp]=i;
pos2[tmp]=j;
}
if(!flag)swap(b1,b2),swap(e1,e2);
outint(e1-b1);pc('\n');
for(int re i=b1+1;i<=e1;++i)outint(i),pc(' ');
pc('\n');
outint(e2-b2);
pc('\n');
for(int re i=b2+1;i<=e2;++i)outint(i),pc(' ');
pc('\n');
return 0;
}