5.5状压dp

本文介绍了状压动态规划在解决一些IT问题中的应用,如种植方案、最短路径和最优组队,强调了利用二进制状态表示和位运算进行状态压缩和判断的技巧。通过例题展示了如何设计转移方程并利用位运算简化状态合法性检查。
摘要由CSDN通过智能技术生成

5.5 状压dp

基本概念

  • 在状态比较多且可以用1/0表示时,往往使用状压dp解题
  • 状压dp的状态设计往往形如 d p i , j dp_{i,j} dpi,j,表示考虑到第i行,状态为j时的方案数
  • 其中j为一个二进制数,j的每一位表示对应行上每一列的状态
  • 由于dp的一个维度是状态压缩产生的二进制数,所以说此类题目数据范围一般较小
  • 在状压dp中,巧妙的位运算是AC的关键

位运算
详见位运算详解+竞赛常见用法总结


例题

例题1:种植方案

  1. 思路
  • n ≤ 12 n\le 12 n12可知,本题考察状压dp
  • d p i , j dp_{i,j} dpi,j表示处理到第i行,田地状态为j时的方案数
  • 显然,只有在j合法(为在相邻格子中种草)时,且上一行状态恰好与本行错开时才可以用i-1行更新i行
  • 此时,有 d p i , j = ∑ d p i − 1 , k dp_{i,j}=\sum dp_{i-1,k} dpi,j=dpi1,k(k为上一行的状态)
  • 如何判断状态是否合法呢?
  • 根据与运算的性质易得,对于一个10交替出现的二进制数,它和另一个恰好与他错开的01交替出现的二进制数进行按位与运算之后结果一定是0,反之一样成立
  • 所以合法性的判断使用简单的位运算即可实现:对于相邻为1的判断,使用该二进制数与其左右移一位之后的数分别进行与(&)运算即可;对于上下两行状态合法性的判断同理,将两个状态进行与运算即可
  • 像这样:
  • chk[i] = (!(i & (i >> 1))) && (!(i & (i << 1)));预处理出这个判断数组后,当以状态数为下标时对应的值为一时,状态合法
  1. 代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 20;
const int V = (1 << 12) + 10, p = 1e8;

int val[N], f[N][V], field[N][N];
bool chk[V];
int m, n;

signed main()
{
    scanf("%lld%lld", &m, &n);
    for (int i = 1; i <= m; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            scanf("%lld", &field[i][j]);
        }
    }
    for (int i = 1; i <= m; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            val[i] = (val[i] << 1) + field[i][j];
        }
    }
    int wall = 1 << n;
    for (int i = 0; i < wall; i++)
    {
        chk[i] = (!(i & (i >> 1))) && (!(i & (i << 1)));
    }
    f[0][0] = 1;
    for (int i = 1; i <= m; i++)
    {
        for (int j = 0; j < wall; j++)
        {
            if (chk[j]&&((j&val[i])==j))
            {
                for (int k = 0; k < wall; k++)
                {
                    if (!(j & k))
                    {
                        f[i][j] = (f[i][j] + f[i - 1][k]) % p;
                    }
                }
            }
        }
    }
    int ans=0;
    for (int i = 0; i < wall; i++)
    {
        ans = (ans + f[m][i]) % p;
    }
    printf("%lld\n", ans);
    return 0;
}

例题2:最短路径

  1. 思路:
  • 观察Hamilton路径的定义,我们可以发现每个点的状态只能为0/1,故该状态可以使用一个二进制数来表示,故有dp状态: d p i , s dp_{i,s} dpi,s表示已经走到i点,此时图中所有点的状态为s,则有转移:
    d p i , s = m i n { d p k , ( s   ˆ ( 1 < < i − 1 ) ) + d i s i , k } , k ≠ i 且 s & ( 1 < < k − 1 ) = = 0 dp_{i,s}=min\{dp_{k,(s\^\ (1<<i-1))}+dis_{i,k}\},k\neq i且s\&(1<<k-1)==0 dpi,s=min{dpk,(s ˆ(1<<i1))+disi,k},k=is&(1<<k1)==0
    即走到k点(没有到过i点)的路径+i走到k的距离
  • 注意状态中各点下标从0开始
  1. 代码:
#include<bits/stdc++.h>
using namespace std;

const int N = 25;

int n,dis[N][N],f[N][(1<<21)];

int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			cin>>dis[i][j];
		}
	}
	memset(f,0x3f,sizeof(f));
	f[1][1]=0;
	for(int j=0;j<(1<<n);j++){
		for(int i=1;i<=n;i++){
			if(!(j&(1<<i-1))) continue;
			for(int k=1;k<=n;k++){
				if(k==i) continue;
				if(!(j&(1<<k-1))) continue;
				f[i][j]=min(f[i][j],f[k][j^(1<<(i-1))]+dis[i][k]);
			}
		}
	}
	cout<<f[n][(1<<n)-1]<<'\n';
	return 0;
}

练习1:最优组队

  1. 思路:
  • 我们发现输入数据满足状压dp的格式,所以可以让输入直接成为dp数组的初值
  • 接着只需枚举子集即可
  1. 一种独特的子集枚举方法:
  • 设原状态为S,我们每次减去lowbit(S),同时&上S,这样就实现了枚举S中所有为1的位置的所有组合,也就是枚举出了拼成S表示的组合的所有方式
for(int i=1;i<=(1<<n)-1;i++){
		int j=i;
		while(j){
			j-=i&(-i);
			j=j&i;
			dp[i]=max(dp[i],dp[j]+dp[i^j]);
		}
	}
  1. 代码:
#include <bits/stdc++.h>
using namespace std;

const int N = 1e6 + 100;

int dp[N], n;

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin >> n;
    for (int i = 1; i <= (1 << n) - 1; i++) {
        cin >> dp[i];
    }
    for (int i = 1; i <= (1 << n) - 1; i++) {
        int j = i;
        while (j) {
            j -= i & (-i);
            j = j & i;//注意每次要&上i
            dp[i] = max(dp[i], dp[j] + dp[i ^ j]);
        }
    }
    cout << dp[(1 << n) - 1] << '\n';
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值