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巨佬
大家为蒟蒻点一个赞好不好?!