用状态压缩表示糖果口味
大佬:我们简单演示一下用状态压缩表示一包糖果中的口味。例如输入一包糖果的“2,3,5”三种口味。要把这 3 个口味压缩成二进制数 10110 ,就需要做“移位”和“或”操作:
定义初始值 tmp = 0
输入口味“2”。先做移位 1<<(2−1),得二进制数 10;然后再与 tmp或,得 tmp= tmp| 10 = 10
输入口味“3”。先做移位 1<<(3−1),得二进制数 100;然后再与 tmp 或,得 tmp= tmp| 100 = 110
输入口味“5”。先做移位1<<(5−1),得二进制数10000;然后再与 tmp或,得 tmp= tmp| 100 = 10110
|的用法
|: 二进制“或”(有1时,结果是1,都是0时,结果为0。),比如:1010 | 1011 = 1011,1010 | 1000 = 1010。 两个数 只要有1就是1
#include<bits/stdc++.h>
using namespace std;
int dp[1<<20]; //dp[v]得到口味为V时所需要的糖果数量
int kw[100]; //第i包糖果的口味 存放状态压缩后数字的数组
int main(){
int n,m,k;
cin >> n >> m >> k ; //共n行 (n包) 每行k个数(一包K颗) 总共有m种口味
int tot = (1<<m) - 1;
memset(dp,-1,sizeof dp);
for(int i = 0;i < n;i++){
int tmp = 0;
for(int j = 0; j < k;j++){
//遍历每行每个数
int x;
cin>>x;
tmp = tmp | (1<<x-1);
}
kw[i] = tmp ;
dp[tmp] = 1; //得到这个压缩状态的数字 需要 1包
}
for(int i = 0;i<=tot;i++){
if(dp[i]!=-1)
//是之前录入的状态
{
for(int j = 0;j<n;j++){
int tmp = kw[j]; // 把状态压缩后的数字给tmp
if(dp[i|tmp] == -1 || dp[i|tmp] > dp[i]+1) //出现了新的组合 || 这个新组合之前的值过大/不是最少的答案
dp[i|tmp] = dp[i]+1;
}
}
}
cout << dp[tot];
return 0;
}
状态压缩的原理
状态压缩 DP 的应用背景是以集合为状态,且集合一般用二进制来表示,用二进制的位运算来处理。
集合问题一般是指数复杂度的,例如:
1.子集问题,设元素无先后关系,那么共有 2^n个子集。
2.排列问题,对所有元素进行全排列,共有 n!n! 个全排列。
位运算
#include<bits/stdc++.h>
int main(){
int a = 213, b = 21; //a = 1101 0101 , b= 0001 1001
printf("a & b = %d\n",a & b); // AND = 17, 二进制0001 0001
printf("a | b = %d\n",a | b); // OR = 221, 二进制1101 1101
printf("a ^ b = %d\n",a ^ b); // XOR = 204, 二进制1100 1100
printf("a << 2 = %d\n",a << 2); // a*4 = 852, 二进制0011 0101 0100
printf("a >> 2 = %d\n",a >> 2); // a/4 = 53, 二进制0011 0101
int i = 5; //(1)a的第i位是否为1
if((1 << (i-1)) & a) printf("a[%d]=%d\n",i,1); //a的第i位是1
else printf("a[%d]=%d\n",i,0); //a的第i位是0
a = 43, i = 5; //(2)把a的第i位改成1。a = 0010 1011
printf("a=%d\n",a | (1<<(i-1))); //a=59, 二进制0011 1011
a = 242; //(3)把a最后的1去掉。 a = 1111 0010
printf("a=%d\n", a & (a-1)); //去掉最后的1。 =240, 二进制1111 0000
return 0;
}