状压dp
1.位运算常用技巧
- 获取二进制的某一位:
x>>i&1
- 或运算,通常在状压dp中用来将两个状态相加
- 修改二进制中的某一位
- 修改第i位为1,用或运算:
x|(1>>i)
- 修改第i位为0,用与运算:
x&(1>>i)
- 修改第i位为1,用或运算:
状态压缩
原理
状态压缩就是使用某种方法来表示某种状态,通常是用一串01数字(二进制数)来表示各个状态。这就要求使用状态压缩的对象的状态必须只有两种,0或1;当然如果有三种状态用三进制来表示也未尝不可。
流程
1.通常用dp数组来存所有状态,所有状态用二进制表示,dp数组开大小为dp[(1<<n)-1]。
2.初始化dp数组,同时将输入数据,初始化每一层对应的状态数组。
3.枚举每一层状态,同时枚举所有状态,并找到相应的状态转移方程。
例题及代码
[蓝桥杯 2019 省 A] 糖果
题解:先开dp数组用来表示所有状态对应的购买数量,并开数组用来存每个包糖果对应的状态数组即每包糖果有哪几个种类的糖果(第i种对应的二进制第i位为1),枚举每一层糖果的状态和所有状态,每个状态或运算当前层糖果状态等于该状态加上选上当前层糖果,由此寻找状态转移方程。
代码如下
#include<bits/stdc++.h>
using namespace std;
int a[200];
int x;
int dp[1<<21];
int n,m,k;
int main()
{
cin>>n>>m>>k;
memset(dp,0x3f,sizeof(dp)); //求最小值,先初始化dp为最大值
dp[0]=0;
for(int i=1;i<=n;++i)
{
for(int j=1;j<=k;++j)
{
cin>>x;
a[i]=(a[i]|(1<<(x-1))); //读入每包糖果的种类状态
}
}
for(int i=1;i<=n;++i)
{
for(int j=0;j<(1<<m);++j)
{
if(dp[j]>n)
continue;
//将原来得到糖果所需的次数与当前次数+1(要这袋糖果)比较并赋值
dp[j|a[i]]=min(dp[j|a[i]],dp[j]+1);
}
}
if(dp[(1<<m)-1]>n) //如果数量超过n包,则无法实现所有口味都买到
cout<<"-1";
else
cout<<dp[(1<<m)-1];
return 0;
}