CF 356D Bags and Coins【背包优化】【构造】

前言

这是蒟蒻的第一篇题解,所以在语言以及证明上没有其他大佬们讲的好。请各位多多包涵,万分感谢!!

本篇题解涉及的芝士有:

1.01背包;

2.bitset优化背包;

3.构造的基本思想

正题

分析

先看题,这道题的题意就是给你很多个袋子,告诉你每个袋子中总共有多少枚硬币,且硬币总数为\(s\)个,袋子与袋子之间可以相互包含。

首先,我们想,袋子与袋子之间的包含关系很像一棵树,对于比它硬币总数小的袋子可以套进该袋子中,像一条链,所以我们先只考虑链的情况,这样就可以把树形转换为链的情况,实际上是不会变的,并且链对输出方案也更加简单了。

这样我们就可以得到多条链,每一条链的链头的和就是\(s\)

然后我们就是要构造出这样的多条链,问题瞬间就变简单了\(QWQ\)

考虑链头,我们可以确定\(a_i\)的最大值一定为链头中的一个,因为它不能放入其他包中。

最后,问题又转化为在排好序的\(a\)数组中,找到包括\(a_n\)在内的多个数,使得和为\(s\)。至于剩下的数,就从大到小放入\(a_n\)的包中,谁叫他是最大的呢\((^_^)\);

分步实现

1.背包转移

找到由\(i\)个数可以凑出的所有值的集合。

我们使用01背包+bitset优化,来得出比\(a_n\)小的数中有没有多个数可以组成\(s-a_n\)。bitset记录在枚举到当前情况下可以组成的值的集合每一位的下标表示值。

转移方程:
\(S|=(S<<a_i)\)

得到新的集合。

2.路径输出

然后就是路径的问题了,我们要倒着求出被选中的数。

于是又维护一个bitset的多维数组,在01背包转移时,每十次记录当前的边集。

再倒回去寻找路径时,记录\(now=s-a_n\),每次\(now-=a_i\),同时\(check(i-1,now-a_i)\),看前\(i-1\)个数能否凑出\(now-a_i\)的值,还是利用所记录的bitset来顺着推。

找到每一个被选中的数之后,打上标记。

把剩下的数从大到小记录前后驱后,放入最大的包中。

输出时,判断这个点有没有后驱,分别输出!

Talk is cheap,show you the code !

/*代码不卡常,码风不毒瘤*/

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+100;
int n,s;
struct node{
	int id,val,in,nxt;
}a[N];

bool cmp1(node a,node b){
	return a.val<b.val;
}
bool cmp2(node a,node b){
	return a.id<b.id;
}
bitset<N> tmp,tmp2,tmpx[N/10];
bool check(int x,int y){//判断前x个数能否凑出y 
	if(y<0){
		return 0;
	}
	tmp2=tmpx[x/10];//历史记录 
	for(int i=x/10*10+1;i<=x;i++){
		tmp2|=tmp2<<a[i].val;//顺着推几步 
	}
	return tmp2[y];
}

int main(){
	cin>>n>>s;
	
	for(int i=1;i<=n;i++){
		cin>>a[i].val;a[i].id=i;
	}
	sort(a+1,a+n+1,cmp1);
	tmp[0]=1;
	tmpx[0]=tmp;
	for(int i=1;i<=n;i++){//bitset优化01背包 
		tmp|=tmp<<a[i].val;
		if(i%10==0){
			tmpx[i/10]=tmp;//记录历史 
		}
	}
	int tar=s-a[n].val;
	if(check(n-1,tar)){//有无解 
		int now=s-a[n].val;
		for(int i=n-1;i;i--){//选数 
			if(now>=a[i].val&&check(i-1,now-a[i].val)){
				now-=a[i].val;
				a[i].in=1;//标记 
			}
		}
		int lst=n;//前驱 
		for(int i=n-1;i;i--){
			if(!a[i].in){
				a[lst].nxt=a[i].id;//连边 
				lst=i;
			}
		}
		sort(a+1,a+n+1,cmp2);//按序号排序 
		for(int i=1;i<=n;i++){
			if(a[i].nxt){
				printf("%d 1 %d\n",a[i].val-a[a[i].nxt].val,a[i].nxt);//输出一条链 
			}else{
				printf("%d 0\n",a[i].val);//单个结点 
			}
		}
	}else{
		cout<<-1<<endl;
	}
	return 0;
}

后记

再次感谢各位看官,希望管理员大大可以通过一下!

Thanks♪(・ω・)ノ

\(QWQ\)

本文思路来源于:_onglu 是ACMER巨佬

大家为蒟蒻点一个赞好不好?!

posted @ 2021-11-12 21:42  SSZX_loser_lcy  阅读(53)  评论(0编辑  收藏  举报