【CF618F】Double Knapsack

题目

题目链接:https://codeforces.com/problemset/problem/618/F

给你两个可重集 \(A,B\),\(A,B\) 的元素个数都为 \(n\)\(n⩽1000000\),它们中每个元素的大小 \(x∈[1,n]\)。请你分别找出 \(A,B\) 的可重子集,使得它们中的元素之和相等。

思路

记两个数组的前缀和分别为 \(a,b\),设 \(a[n]\geq b[n]\)。对于每一个 \(a[i]\),求出小于 \(a[i]\) 且最接近 \(a[i]\)\(b[j]\),令 \(c[i]\) 表示 \(a[i]-b[j]\)

如果存在两个 \(c[i]\)\(c[j]\) 相等,那么就一定存在答案。由于 \(0\leq c< n\),而一共有 \(c[0]\sim c[n]\)\(n+1\) 个不同的答案,所以一定有答案,且一定有连续的答案。

时间复杂度 \(O(n)\)

代码

#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;

const int N=1000010;
int n,pos[N][2];
ll a[N],b[N];
bool flag;

ll read()
{
	ll d=0; char ch=getchar();
	while (!isdigit(ch)) ch=getchar();
	while (isdigit(ch)) d=(d<<3)+(d<<1)+ch-48,ch=getchar();
	return d;
}

int main()
{
	n=read();
	for (int i=1;i<=n;i++)
		a[i]=a[i-1]+read();
	for (int i=1;i<=n;i++)
		b[i]=b[i-1]+read();
	if (a[n]<b[n])
	{
		for (int i=1;i<=n;i++)
			swap(a[i],b[i]);
		flag=1;
	}
	memset(pos,-1,sizeof(pos));
	for (int i=0,j=0;i<=n;i++)
	{
		while (j<n && a[i]>=b[j+1]) j++;
		int k=a[i]-b[j];
		if (pos[k][0]>=0)
		{
			if (flag) swap(i,j),swap(pos[k][0],pos[k][1]);
			printf("%d\n",i-pos[k][0]);
			for (int l=pos[k][0]+1;l<=i;l++) printf("%d ",l);
			printf("\n%d\n",j-pos[k][1]);
			for (int l=pos[k][1]+1;l<=j;l++) printf("%d ",l);
			return 0;
		}
		pos[k][0]=i; pos[k][1]=j;
	}
	return 0;
}
posted @ 2020-05-28 11:32  stoorz  阅读(...)  评论(...编辑  收藏