0
点赞
收藏
分享

微信扫一扫

蓝桥杯 糖果(IDA*+状压+剪枝)

左手梦圆 2022-02-02 阅读 39

 


解法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;
		
}
举报

相关推荐

0 条评论