动态规划模型总结之状压dp

版权声明:版权所有,转载请标明博文出处。 https://blog.csdn.net/HQG_AC/article/details/86475227

啊怎么前面的都没写就直接状压了啊

状压比较简单先讲状压(其实比较向深搜的优化)

可能蓝书上的例题偏难先将一些简单的

不会位运算就gg了吧

1.问题引入

为什么需要状压dp

状压dp可以解决一些题目的状态随阶段增长而增长的题目,比如一个量用了还是没用等等,把状态压缩成数字存入数组然后进行dp

啊听着好抽象啊

那我们搞一个例题看看

状压dp入门题:玉米田

简化后的题目意思是有一块矩阵,可以取一些1,但是不能有相邻的1,问有多少种取法

此题是否可以 d f s dfs ?应该是可以的,但是会TLE

我们发现在 d f s dfs 的是后需要记录当前位置取没取,这样才能判断该位置可不可以去

这提示我们可以使用状压 d p dp

我们首先预处理出每一行有哪些取的方案合法,即不能取0也不能有相邻

不能取0比较简单,也就是把每行的草地情况压成int(存到 f f 数组),然后如果枚举到一个状态 s t a sta ,如何判断这个 s t a sta 没有零?就是他与该行的 f f 的且是否还是原数,因为如果不是原数,说明他选取了一些0,否则肯定是合法的

那如何判断一个状态没有相邻的?这个就要考察位运算的功底了,其实就是左移一位和右移一位都与原数没有交

预处理出这些就可以进入最后的 d p dp 环节

d p [ i ] [ s t a ] dp[i][sta] 表示第 i i 行现在的状态为 s t a sta

然后枚举之前的状态为 m a s k mask ,如果 s t a sta m a s k mask 没有交,则把 d p [ i 1 ] [ m a s k ] dp[i-1][mask] 的值加到 d p [ i ] [ s t a ] dp[i][sta] 里面去

答案就是 d p [ n ] [ s t a ] \sum{}dp[n][sta]

我们发现如果第 i i 行的取法只与 i 1 i-1 行和他自己有关,于是可以把 i i 这一维滚动掉

时间复杂度 O ( n 2 n 2 n ) O(n*2^n*2^n)

n n 才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

题目描述

n n 个装着水的开水瓶,要把它们中的水汇总到不超过 k k 个开水瓶里,把第 i i 个开水瓶里的水全都倒到第 j j 个开水瓶里(无论它们现在装了多少水)的代价是 c i j c_{ij} 。求最小总代价。

输入格式

第一行两个整数 n , k n,k

接下来的 n n 行,每行 n n 个整数表示 c i j c_{ij} ,保证 c i i = 0 c_{ii}=0

输出格式

输出一行一个整数表示答案。

样例

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

数据范围

1 k n 20 , c i j 1 0 5 1≤k≤n≤20,c_{ij}≤10^5

如果您上一个题目听懂了,这个就是小case了

d p [ m a s k ] dp[mask] 表示瓶子状态为 m a s k mask 最小需要花费的代价

然后枚举把 i i 号瓶子的水转移到 j j 号瓶子就好了

然后如果中间1的个数 &lt; = k &lt;=k 的话就更新答案

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) ;
}

猜你喜欢

转载自blog.csdn.net/HQG_AC/article/details/86475227