状态压缩+DP例题

2021 E回路计数

**> 问题描述**
蓝桥学院由 21 栋教学楼组成,教学楼编号 121。
对于两栋教学楼 a 和 b,当 a 和 b 互质时,a 和 b 之间有一条走廊直接相连,两个方向皆可通行,
否则没有
连接的走廊。
小蓝现在在第一栋教学楼,他想要访问每栋教学楼正好一次,最终回到第一栋教学楼(即走一条哈密尔顿回路),
请问他有多少种不同的访问方案?两个访问方案不同是指存在某个 i,小蓝在两个访问方法中访问完教学楼 i 后
访问了不同的教学楼。
提示:建议使用计算机编程解决问题。

答案: 881012367360

思路:状态压缩+DP

在这里插入图片描述

代码:

#include<iostream>
using namespace std;
typedef long long ll;
//求 a,b最大公约数,互质数---两个数的最大公约数是1 
int gcd(int a, int b)//6 4 
{
	if(a%b==0) return b;
	else if(b%a==0) return a;
	else if(a>b) return gcd(a-b, b);
	else return gcd(b-a, a);
}
const int n = 21, m = 1 << n;
ll dp[m][n];//dp[i][j]:走到j点时,状态为i时的方案总数 
bool e[n][n];//标记两点是否互质 

int main()
{
	for (int i=1; i<=21;i++)
		for (int j = 1; j <= 21; j ++)
			if(gcd(i,j)==1)
				e[i - 1][j - 1] = true;//互质为true,且把21个点标号为0~20 
				
	dp[1][0] = 1;//初始化:走到第一个点是状态为0001的方案数为1; 
	for (int i = 1; i <= m - 1; i ++) {
	//m=1<<21即22个1的二进制,m-1是21个1的二进制 
		for (int j = 0; j <= 20; j ++){
		
			if(i >> j & 1)//j这栋楼我是不是访问过,访问过才向下走; 
				for (int k = 0; k <= 20; k ++){
				
					if(((i>>k)&1)&&e[k][j])
			//状态转移方程:走到j栋楼状态为i的方案总数=
			//走到j之前的那个状态(i-(1<<j)),对应的最后楼栋是任意一栋满足条件的,k循环;
			//条件是i这个状态已访问过k--(i>>k)&1==1;并且k和j互质,即k能走到j.
						dp[i][j]+= dp[i-(1<<j)][k];
					}
			}  
}
	ll ans = 0;
for (int i=1;i<=20;i++)//楼栋数0~20 ,回到第一栋楼前的最后一栋楼可以是标号1~20的任意一 
  ans+=dp[m-1][i];//状态m-1是21个1的二进制,i是除了第一栋 所有楼栋循环 
	
	cout<<ans<<endl;	
	return 0;								
}

2019 第九题:糖果

(状态压缩+DP)

题目描述 糖果店的老板一共有 M 种口味的糖果出售。为了方便描述,我们将 M 种口味编号 1 ∼ M。 小明希望能品尝到所有口味的糖果。遗憾的是老板并不单独出售糖果,而 是 K 颗一包整包出售。 幸好糖果包装上注明了其中 K
颗糖果的口味,所以小明可以在买之前就知道每包内的糖果口味。 给定 N 包糖果,请你计算小明最少买几包,就可以品尝到所有口味的糖果。
【输入格式】 第一行包含三个整数 N、M 和 K。 接下来 N 行每行 K 这整数 T1, T2, · · · , TK,代表一包糖果的口味。
【输出格式】 一个整数表示答案。如果小明无法品尝所有口味,输出 −1。
【样例输入】
6 5 3
1 1 2
1 2 3
1 1 3
2 3 5
5 4 2
5 1 2
【样例输出】 2
【评测用例规模与约定】
对于 30% 的评测用例,1 ≤ N ≤ 20 。
对于所有评测样例,1 ≤ N ≤ 100,1 ≤ M ≤ 20,1 ≤ K ≤ 20,1 ≤ Ti ≤ M 。

在这里插入图片描述

#include <bits/stdc++.h>
#include <algorithm>
using namespace std;
const int maxn=25;
int a[105][maxn],f[(1<<20)+5],b[maxn];
int main(){
	int n,m,k;
	cin>>n>>m>>k;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=k;j++){
			cin>>a[i][j];
			b[i]|=1<<(a[i][j]-1);
			//例如第一包 00001|00010=00011 
		}
	}
for(int i=1;i<=(1<<m)-1;i++)
	   f[i]=-1; 
//f[i][j] 前i包糖果达到状态j,至少需要多少包	     
for(int i=1;i<=n;i++){
	for(int j=0;j<=(1<<m)-1;j++){
		
		if(f[j]!=-1)  //之前已经达到j状态 
		f[j|b[i]]=(f[j|b[i]]==-1?f[j]+1:min(f[j]+1,f[j|b[i]]));
		//j|b[i]  选择了第i包可以达成的状态
		}
    }	
cout<<f[(1<<m)-1];
}
/*
6 5 3
1 1 2
1 2 3
1 1 3
2 3 5
5 4 2
5 1 2
*/

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值