题目链接:活动 - AcWing
输入样例:
6 5 3
1 1 2
1 2 3
1 1 3
2 3 5
5 4 2
5 1 2
输出样例:
2
分析:
这道题我一开始时用搜索+剪枝写的
首先需要用一个整数对每一包糖果所含的糖果种类进行状压表示
搜索函数里面有三个参数now,st和cnt,now表示当前考虑第now包糖果,st表示当前已经选取的糖果的状态,cnt表示当前已经选取的糖果包数
对每一包糖果我们有两种选择,一种是选,另一种是不选这也是我们搜索的方向,这样就完成了搜索思路的讲解,提交发现只能过45%的数据,其余超时,于是我们可以进行剪枝,思路如下:
假如我们当前考虑是否选取第i包糖果,状态为st,假如把第i包糖果及之后的糖果全部选取也无法包含所有的糖果那么这种情况我们就没有必要继续进行搜索了,这也就是我们剪枝的地方。
设置一个数组S[i]表示s[i] | s[i+1] | …… | s[n]的结果(s[i]为第i包糖果的所有糖果的状态表示)
那么剪枝条件就是当遍历到第i包糖果且状态为st时的剪枝条件就是 S[i] | st != End
进行这样的剪枝后就能够过80%的数据,还有20%的数据是超时,下面附上优化后的搜索代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#include<cstring>
using namespace std;
const int N=103;
int s[N],End,S[N];//S[i]是s[i]|s[i+1]|……|s[n]的结果
int n,m,ans=0x3f3f3f3f;
void dfs(int now,int st,int cnt)
{
if((st|S[now])!=End||cnt>=ans) return ;
if(st==End)
{
ans=min(ans,cnt);
return ;
}
if(now>n) return ;
dfs(now+1,st,cnt);
dfs(now+1,st|s[now],cnt+1);
}
int main()
{
int k;
cin>>n>>m>>k;
End=(1<<m)-1;
for(int i=1;i<=n;i++)
{
int t;
for(int j=1;j<=k;j++)
{
cin>>t;
s[i]|=(1<<(t-1));
}
}
for(int i=n;i>=1;i--)
S[i]=S[i+1]|s[i];
dfs(1,0,0);
if(ans==0x3f3f3f3f) ans=-1;
cout<<ans;
return 0;
}
但是大家有没有发现这道题与背包问题比较相似,就是说对于每种物品我们可以选也可以不选,而这两种情况我们可以直接在for循环里面实现,
设f[i]为糖果状态为i时所需要选取的最小包数,那么答案就是f[End],枚举所有的状态st,把第i包糖果选取后的状态为st | s[i],那么f[ st | s[i] ]=min(f[ st | s[i] ],f[ s[i] ]+1),这是比较容易理解的,利用这个状态转移方程我们就可以枚举所有的状态,而且复杂度也不算太高,可以通过所有数据。
下面是代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#include<cstring>
using namespace std;
const int N=1<<21;
int f[N];//f[i]为糖果状态为i时所需要选取的最小包数
int End;//End为最终状态
int s[N];//s[i]为第i包糖果所含的糖果口味的集合
int main()
{
int n,m,k;
cin>>n>>m>>k;
End=(1<<m)-1;
for(int i=1;i<=n;i++)
{
int t;
for(int j=1;j<=k;j++)
{
cin>>t;
s[i]|=(1<<(t-1));
}
}
memset(f,0x3f,sizeof f);
f[0]=0;
for(int i=1;i<=n;i++)
for(int st=0;st<=End;st++)
f[st|s[i]]=min(f[st|s[i]],f[st]+1);
if(f[End]==0x3f3f3f3f) f[End]=-1;
cout<<f[End];
return 0;
}