啊怎么前面的都没写就直接状压了啊
状压比较简单先讲状压(其实比较向深搜的优化)
可能蓝书上的例题偏难先将一些简单的
不会位运算就gg了吧
1.问题引入
为什么需要状压dp
状压dp可以解决一些题目的状态随阶段增长而增长的题目,比如一个量用了还是没用等等,把状态压缩成数字存入数组然后进行dp
啊听着好抽象啊
那我们搞一个例题看看
简化后的题目意思是有一块矩阵,可以取一些1,但是不能有相邻的1,问有多少种取法
此题是否可以 ?应该是可以的,但是会TLE
我们发现在 的是后需要记录当前位置取没取,这样才能判断该位置可不可以去
这提示我们可以使用状压
我们首先预处理出每一行有哪些取的方案合法,即不能取0也不能有相邻
不能取0比较简单,也就是把每行的草地情况压成int(存到 数组),然后如果枚举到一个状态 ,如何判断这个 没有零?就是他与该行的 的且是否还是原数,因为如果不是原数,说明他选取了一些0,否则肯定是合法的
那如何判断一个状态没有相邻的?这个就要考察位运算的功底了,其实就是左移一位和右移一位都与原数没有交
预处理出这些就可以进入最后的 环节
表示第 行现在的状态为
然后枚举之前的状态为 ,如果 与 没有交,则把 的值加到 里面去
答案就是
我们发现如果第 行的取法只与 行和他自己有关,于是可以把 这一维滚动掉
时间复杂度
才12,正好
const int p = 100000000 ;
int dp[13][5000],a[13][13],f[13],ok[5000];
int n,m ;
int main(){
scanf("%d%d",&n,&m) ;
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
scanf("%d",&a[i][j]) ;
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
f[i]=(f[i]<<1)+a[i][j] ;
for (int i=0;i<(1<<m);i++) ok[i]=((!(i&(i<<1)))&&(!(i&(i>>1)))) ;
dp[0][0]=1 ;
for (int i=1;i<=n;i++){
for (int j=0;j<(1<<m);j++) {
if (ok[j] && ((j&f[i])==j)){
for (int k=0;k<(1<<m);k++)
if (!(j&k)){
dp[i][j]=(dp[i][j]+dp[i-1][k])%p ;
}
}
}
}
int ans=0;
for (int i=0;i<(1<<m);i++) ans=(ans+dp[n][i])%p ;
printf("%d\n",ans) ;
}
如果您觉得这个题目还比较困难,不妨先做一个小练习:
Water
题目描述
有 个装着水的开水瓶,要把它们中的水汇总到不超过 个开水瓶里,把第 个开水瓶里的水全都倒到第 个开水瓶里(无论它们现在装了多少水)的代价是 。求最小总代价。
输入格式
第一行两个整数 。
接下来的 行,每行 个整数表示 ,保证 。
输出格式
输出一行一个整数表示答案。
样例
input
5 2
0 5 4 3 2
7 0 4 4 4
3 3 0 1 2
4 3 1 0 5
4 5 5 5 0
output
5
数据范围
。
如果您上一个题目听懂了,这个就是小case了
表示瓶子状态为 最小需要花费的代价
然后枚举把 号瓶子的水转移到 号瓶子就好了
然后如果中间1的个数 的话就更新答案
int n, k, ans = iinf ;
int dp[N], a[25][25] ;
int calc(int x) {
int res = 0 ;
while (x) {
x = x & x - 1 ;
res++ ;
}
return res ;
}
signed main(){
scanf("%d%d", &n, &k) ;
if (n == k) print(0) ;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
scanf("%d", &a[i][j]) ;
for (int i = 0; i <= (1 << n) - 1; i++) dp[i] = iinf ;
dp[(1 << n) - 1] = 0 ;
for (int s = (1 << n) - 1; s >= 0; s--) { // 枚举当前状态
if (calc(s) <= k) ans = min(ans, dp[s]) ;
for (int i = 0; i < n; i++) // 把第i个水壶中的水
if (s & (1 << i))
for (int j = 0; j < n; j++) // 转移到第j个水壶中
if (i != j) {
dp[(s - (1 << i)) | (1 << j)] = min(dp[(s - (1 << i)) | (1 << j)], dp[s] + a[i][j]) ;
}
}
printf("%d\n", ans) ;
}