2018.09.27【CodeForces618F】Double Knapsack(抽屉原理)

传送门


解析:

我一开始还以为要用bitset维护一下出现了哪些和,但是后来发现发现bitset不好输出解。

思路:

其实我们为什么要维护出现过哪些和呢?
我们可以维护一下前缀和。
我们试着枚举元素总和比较小的的集合的每个前缀和。
然后我们在元素总和大的集合中调整指针,使得当前前缀刚好不小于我们刚才枚举的前缀和。

然后是关键的一步,我们计算前缀差,查询前面是否出现过同样的前缀差。

如果查询到一样的前缀差,那么当前的两个前缀分别减去之前前缀差相同的前缀就能得到我们要的元素和相同的区间。有可能前缀和从0开始,我们要从第0个位置开始枚举。

没有查询到就是无解。

那么,还记不记得标题?抽屉原理?

这关抽屉原理什么事?

那么你再想一想,我们枚举并查询了多少个前缀差?
从0开始,到n结束,一共 n + 1 n+1 n+1种。
而实际上按照刚才的策略,有多少前缀差可能存在?
只有 n n n种,0到 n − 1 n-1 n1。为什么?

如果前缀差出现了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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值