题目链接:http://codeforces.com/contest/618/problem/F
题目:
题目大意:
有两个大小为 N 的可重集 A, B, 每个元素都在 1 到 N 之间. 分别找出 A 和 B 的一个子集, 使得这两个子集元素之和相等.
分别输出集合A和集合B的子集的个数以及子集中元素在原集合中的位置
N ≤ $10^6$
题解:
首先我们证明一个结论,存在一组方案,满足两个子集在A中和在B中都是连续的一段
把两个集合看成两个数组,分别计算出前缀和sa,sb
对于每个i(0<=i<=n),我们j为满足0<=sa[i]-sb[j]<=n-1的最大的j,设d[i]=sa[i]-sb[j],可以发现j的单调递增的
我们发现d数组一共有n+1个元素(包括i=0,sa[i]=0的情况),并且我们又发现d[i]的取值只有n个。那么根据抽屉原理,至少有两个d数组中的元素是相等的
于是我们有sa[i']-sb[j']=sa[i]-sb[j](i'>i)
移项之后得到sa[i']-sa[i]=sb[j']-sb[j]
这个时候我们就知道在A数组中i+1~i'这一段元素之和与B数组中j+1~j'这一段元素之和相等
证毕
其实上面的证明过程就是我们本题的做法,我们用two pointers处理出d数组,然后判断当前的d值在之前是否出现过,这个时候我们就得到了答案
值得注意的是,我们需要sa[n]<=sb[n],因为若是sa[n]>sb[n]可能出现对于i=n找不到一个j满足上述条件
代码:
#include<iostream> #include<algorithm> #include<cstring> #include<cstdio> using namespace std; const int N=1e6+15; int n,cnt1,cnt2,st1,st2,ed1,ed2; int sa[N],sb[N],vis[N],p[N]; inline int read() { char ch=getchar(); int s=0,f=1; while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();} while (ch>='0'&&ch<='9') {s=(s<<3)+(s<<1)+ch-'0';ch=getchar();} return s*f; } void solve(int a[],int b[]) { memset(vis,-1,sizeof(vis)); int j=0; for (int i=0;i<=n;i++) { while (a[i]-b[j]>=n) j++; int d=a[i]-b[j]; if (vis[d]!=-1) { cnt1=i-vis[d]; st1=vis[d]+1;ed1=i; cnt2=j-p[vis[d]]; st2=p[vis[d]]+1;ed2=j; break; } vis[d]=i;p[i]=j; } } int main() { n=read(); for (int i=1;i<=n;i++) sa[i]=sa[i-1]+read(); for (int i=1;i<=n;i++) sb[i]=sb[i-1]+read(); if (sa[n]<=sb[n]) { solve(sa,sb); } else { solve(sb,sa);swap(cnt1,cnt2);swap(st1,st2);swap(ed1,ed2); } printf("%d ",cnt1); for (int i=st1;i<=ed1;i++) printf("%d ",i); printf(" "); printf("%d ",cnt2); for (int i=st2;i<=ed2;i++) printf("%d ",i); printf(" "); return 0; }