(蓝桥真题) 糖果(IDA*+状压搜索)

 样例输入:

6 5 3
1 1 2
1 2 3
1 1 3
2 3 5
5 4 2
5 1 2

样例输出:

2

分析:这道题目属于经典题目,本质上就是搜索,然后需要加一些贪心的思想,再加上剪枝就能够解决。

看到只有20种糖果,我们显然可以把每包糖果的状态用一个int型数据表示,那么可以用一个vector<int>p[i]记录包含第i类糖果的包都有哪些。然后我们就枚举状态进行搜索就行,搜索过程如下:

每次从所有还没有的糖果中选择一个可选包数最少的那种糖果进行搜索,比如假如当前状态缺少第一类糖果和第二类糖果,其中有5包糖果含有第一类糖果,有3包糖果含有第二类糖果,那么我们肯定先搜索第二类糖果,因为这样我们要搜索的方向就会少很多,复杂度就会少很多。然后我们还有一个玄学优化操作,就是依次增加搜索深度,什么意思呢?就是我们首先设置搜索深度为1,也就是最多选取一包糖果,如果不能找到答案那我们就增大搜索深度,将搜索深度设置为2,然后再去尝试搜索答案,直至搜索到答案为止。这样我们在搜索函数中就会有一个参数来表示当前还可以选择多少包糖果,我们还可以对这个进行剪枝,怎么剪枝呢?因为我们的参数中肯定还会包含当前已经拥有的糖果的状态,那么我们假如当前是没有第5种糖果的,那么我们至少需要选取一包糖果是包含第5种糖果的,那么就把所有包含第5种糖果的包全都选了,而且只减少一次选取次数,那么这种方法一定不会比最优效果差,也就是无论我们怎么进行最优选取都不可能优于这种效果,所以如果按照这种方法我们都不能在规定深度内找到最优解,那么最优解是一定不在我们当前的搜索深度的,就是利用这个原理来进行剪枝的。

值得注意的是该方法还是建立在搜索的基础上进行的,只是进行了可行性剪枝和优化。

细节见代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=123; 
vector<int>p[N];//p[i]记录包括第i种糖果的包的糖果状态 
int Log2[1<<21];
int n,m,k; 
int lowbit(int x)
{
	return x&-x;
}
bool check(int now,int st)//剪枝
{
	for(int i=(1<<m)-1-st;i;i-=lowbit(i))
	{
		int c=Log2[lowbit(i)];
		if(st&(1<<c)) continue;//说明当前状态已经有第c种糖果了 
		now--;
		for(int j=0;j<p[c].size();j++)//每次把所有包含第c种糖果的都选了,而且只相当于选了一包 
			st|=p[c][j]; 
	}
	return now>=0; 
}
bool dfs(int now,int st)//now是当前还能够选取的糖果包数,st是当前已经拥有的糖果种类 
{
	if(now==0) return st==(1<<m)-1;
	if(!check(now,st)) return false; 
	int t=-1;//记录下次要优先选取的糖果种类 
	for(int i=(1<<m)-1-st;i;i-=lowbit(i))
	{
		int c=Log2[lowbit(i)];
		if(t==-1||p[t].size()>p[c].size())
			t=c;
	}
	for(int i=0;i<p[t].size();i++)//枚举包含第t类糖果的包
		if(dfs(now-1,st|p[t][i]))
			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,t;
		for(int j=1;j<=k;j++)
		{
			scanf("%d",&t);
			st|=1<<t-1;
		}
		for(int j=st;j;j-=lowbit(j))
			p[Log2[lowbit(j)]].push_back(st);
	}
	int depth=1;//枚举选的糖果包数,依次增大 
	while(depth<=m&&!dfs(depth,0))
		depth++;
	if(depth>m) puts("-1");
	else printf("%d",depth);
	return 0;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值