解法1:DFS+剪枝(勉强70%) 普通剪枝会TLE
#include<iostream>
#include<algorithm>
#include<map>
#include<stdio.h>
#include<vector>
using namespace std;
typedef long long ll;
const int N=105;
vector<int> vc[N];
int n,m,k;
int res=0x3f3f3f3f;
int vis[25];
void dfs(int pos,int u,int now)
{
if(pos==n+1) return;
if(u>=res) return; //剪枝
if(now==m)
{
res=min(res,u);
return;
}
int cnt=0;
for(int i=0;i<k;i++)
{
int tmp=vc[pos][i];
if(!vis[tmp])
cnt++;
vis[tmp]++;
}
if(!cnt)//剪枝
{
for(int i=0;i<k;i++)
{
int tmp=vc[pos][i];
vis[tmp]--;
}
return;
}
dfs(pos+1,u+1,now+cnt);
for(int i=0;i<k;i++)
{
int tmp=vc[pos][i];
vis[tmp]--;
}
dfs(pos+1,u,now);
}
int main()
{
//freopen("input1.txt","r",stdin);
cin>>n>>m>>k;
for(int i=1;i<=n;i++)
for(int j=1;j<=k;j++)
{
int x;cin>>x;
vc[i].push_back(x);
}
dfs(1,0,0);
if(res==0x3f3f3f3f) cout<<-1;
else cout<<res;
}
解法2:DFS+状压+剪枝(100%)
将每个包裹的口味用二进制表示,提高效率
剪枝:
1.存在更优答案
2.当前状态被搜索过且存在更优解
#include<iostream>
#include<algorithm>
#include<map>
#include<stdio.h>
#include<cstring>
#include<vector>
using namespace std;
typedef long long ll;
const int N=105,M=1<<20;
int a[N];
int h[M];
int n,m,k;
int res=0x3f3f3f3f;
void dfs(int pos,int st,int u)//包裹编号 状态 已取个数
{
if(pos==n+1) return;
if(u>=res) return;
if(h[st]<=u) return;//当前状态已经搜索过且有更优解
if(st==(1<<m)-1)
{
res=min(res,u);
return;
}
h[st]=u;
for(int i=pos;i<=n;i++)
if((st|a[i])!=st) dfs(pos+1,st|a[i],u+1);
}
int main()
{
cin>>n>>m>>k;
for(int i=1;i<=n;i++)
for(int j=1;j<=k;j++)
{
int x;cin>>x;
a[i]|=1<<x-1;
}
memset(h,0x3f,sizeof h);
dfs(1,0,0);
if(res==0x3f3f3f3f) cout<<-1;
else cout<<res;
}
解法3:IDA*+状压+剪枝(100%)
对于每个口味,存储包含其的包裹编号,搜索时选择未得到得口味中,包裹选择(分支)最少的口味。估价函数:当前状态s最少还需要取多少包裹。
#include<iostream>
#include<algorithm>
#include<map>
#include<stdio.h>
#include<vector>
using namespace std;
typedef long long ll;
const int N=105,M=1<<20;
vector<int> vc[N];//vc[i] 包含i这个口味的所有状态
int n,m,k;
int log2[M];
//IDA*
int lowbit(int x)
{
return x&-x;
}
int h(int st)//估价函数
{
int res=0;//当前状态st还需要取的包裹数量
for(int i=(1<<m)-1-st;i;i-=lowbit(i))
{
int c=log2[lowbit(i)];
res++;
for(int j=0;j<vc[c].size();j++)
{
int g=vc[c][j];
i&=~g;//将状态g里的1 对应i里的1清空
}
}
return res;
}
bool dfs(int depth,int st)
{
//搜索完n层(取了n个包裹) 或者 当前需要取的包裹个数大于剩余可取个数
if(!depth||h(st)>depth)
{
if(st==(1<<m)-1)//所有的口味都得到
return true;
return false;
}
int t=-1;
//枚举此状态下,所有未得到的口味中,选择最少的那种方案
for(int i=(1<<m)-1-st;i;i-=lowbit(i))
{
int c=log2[lowbit(i)];//包裹编号
if(t==-1||vc[t].size()>vc[c].size())
t=c;
}
for(int i=0;i<vc[t].size();i++)
{//枚举对于这种口味的所有选择
int g=vc[t][i];//选择的状态
if(dfs(depth-1,st|g))//寻找下一层
return true;
}
return false;
}
int main()
{
cin>>n>>m>>k;
for(int i=0;i<m;i++) log2[1<<i]=i;
for(int i=1;i<=n;i++)
{
int st=0;
for(int j=0;j<k;j++)
{
int x;cin>>x;
st|=1<<x-1;//状态中0~m-1位 表示 第1~m个口味
}
for(int j=0;j<m;j++)
if(st>>j&1)
vc[j].push_back(st);
}
int depth=0;//这里的迭代深度代表的就是选择包裹个数
while(depth<=m&&!dfs(depth,0))
depth++;
if(depth>m) cout<<-1;
else cout<<depth;
}