Codeforces.618F.Double Knapsack(构造 鸽巢原理)

题目链接


(Description)

给定两个大小为(n)的可重集合(A,B),集合中的元素都在([1,n])内。你需要从这两个集合中各选一个非空子集,使它们的和相等。输出方案。
(nleq10^6)

(Solution)

求子集是假的...对两个集合按任意顺序求个前缀和,记为(SA_i,SB_i)。不妨假设(SA_nleq SB_n)
那么能发现,对于每个(SA_i (0leq ileq n)),找出最大的(SB_jleq SA_i)(j)(SA_i-SB_j)的取值范围是([0,n-1])(如果(geq n)则可以移动(j)),只有(n)种。而(i)的取值有(n+1)种。由鸽巢原理,那么一定存在一对(i,i' (i eq i')),使得(SA_i-SB_j=SA_{i'}-SB_{j'})。因为元素大于(0),所以(j eq j')
那么有(SA_i-SA_{i'}=SB_j-SB_{j'}),就可以得到答案啦。


//421ms	23800KB
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#define gc() getchar()
#define MAXIN 500000
//#define gc() (SS==TT&&(TT=(SS=IN)+fread(IN,1,MAXIN,stdin),SS==TT)?EOF:*SS++)
typedef long long LL;
const int N=1e6+5;

LL sa[N],sb[N];
char IN[MAXIN],*SS=IN,*TT=IN;

inline int read()
{
	int now=0;register char c=gc();
	for(;!isdigit(c);c=gc());
	for(;isdigit(c);now=now*10+c-48,c=gc());
	return now;
}
void Solve(const int n,LL *sa,LL *sb,int &la,int &ra,int &lb,int &rb)
{
	static int vis[N],vis2[N];
	sb[n+1]=1ll<<60;
	memset(vis,0xff,sizeof vis);
	for(int i=0,j=0,v; i<=n; ++i)
	{
		while(sb[j+1]<=sa[i]) ++j;
		if(~vis[v=sa[i]-sb[j]]) {la=vis[v], ra=i, lb=vis2[v], rb=j; break;}
		vis[v]=i, vis2[v]=j;
	}
}

int main()
{
	int 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();
	int la,ra,lb,rb;
	if(sa[n]>sb[n]) Solve(n,sb,sa,lb,rb,la,ra);
	else Solve(n,sa,sb,la,ra,lb,rb);
	printf("%d
",ra-la);
	for(int i=la+1; i<=ra; ++i) printf("%d ",i); putchar('
');
	printf("%d
",rb-lb);
	for(int i=lb+1; i<=rb; ++i) printf("%d ",i); putchar('
');

	return 0;
}
原文地址:https://www.cnblogs.com/SovietPower/p/10519255.html