状压dp

蒙德里安的理想

思路:
状态表示

首先,可以枚举横着放小方块的情况,然后再枚举竖着放的情况。
d p [ i ] dp[i] dp[i]来表示前 i − 1 i-1 i1列排放好时排第 i i i列的方案数,但是这样的话似乎无法转移,因为第 i i i列如何排放需要受到第 i − 1 i - 1 i1列的影响,所以必须要表示出第 i − 1 i - 1 i1列的状态。

表示状态可以用01序列表示,1表示该行是否被划分(注意由于划分出来的方块是1x2的而且枚举是横着的所以如果该行可以填充,则前一列的该行必须也要是空的)而01序列可以看做是某个数的二进制,从而我们把每个列的状态映射到了整数上。

d p [ i ] [ j ] dp[i][j] dp[i][j]表示第 i i i列按照 j j j方式摆放的方案数。

状态转移

对于 d p [ i ] [ j ] dp[i][j] dp[i][j]可以由所有的合法 d p [ i − 1 ] [ k ] dp[i - 1][k] dp[i1][k]转移而来
合法的条件是,相邻两行不能重叠,竖直方向的连续空格必须是偶数倍
由于竖着的分割方式只有一种,所以总方案数等于横着分割的方案数

代码
#include<bits/stdc++.h>
#define ll long long
#define eps 1e-8
#define INF 0x3f3f3f3f
#define pb push_back
#define endl '\n'
#define IO ios::sync_with_stdio(false)
using namespace std;
const ll N = 12,M = 1 << 12;
const ll mod = 1e9 + 7;
ll dp[N][M],n,m;
bool st[M];
vector<int> state[M];
int main(){
    IO;
    while(cin >> n >> m && n * m){
        for(int i = 0; i < (1 << n); i++){//计算列方向是否合法若第i中状态合法则st[i] = true;
            int cnt = 0,f = 1;
            for(int j = 0; j < n; j++)
                if((i >> j) & 1){
                    if(cnt & 1){
                        f = 0;
                        break;
                    }
                    cnt = 0;
                }else cnt++;
            if(cnt & 1)st[i] = false;
            else st[i] = true;
        }
        for(int i = 0; i < (1 << n); i++){//state[i]里表示的是能转移到i的状态
            state[i].clear();
            for(int j = 0; j < (1 << n);j++)
                if((i & j) == 0 && st[i | j])
                    state[i].pb(j);
        }
        memset(dp,0,sizeof dp);
        dp[0][0] = 1;
        for(int i = 1; i <= m; i++)
            for(int j = 0; j < (1 << n); j++){
                for(auto x : state[j])
                    dp[i][j] += dp[i - 1][x];
        }
        cout << dp[m][0] << endl;
    }
    return 0;
}

最短Hamilton路径

思路:
状态表示:

考虑 d p [ i ] [ j ] dp[i][j] dp[i][j]表示到达第 i i i个点时走过的点的状态为 j j j所需要花费的最小路程

状态转移:

对于 d p [ i ] [ j ] dp[i][j] dp[i][j]可以考虑它能往哪里转移,也就是它下一步可以走到哪里,对于 d p [ i ] [ j ] dp[i][j] dp[i][j]若下一步能走k则有$dp[k][j + (1 << k)] = min(dp[i][j] + dis[i][k], dp[i][j + (1 << k)])

代码:
#include<bits/stdc++.h>
#define ll long long
#define eps 1e-8
#define INF 0x3f3f3f3f
#define pb push_back
#define endl '\n'
#define IO ios::sync_with_stdio(false)
using namespace std;
const ll N = 21,M = 1 << N;
const ll mod = 1e9 + 7;
int n,G[N][N],bit[N];
int dp[N][M];
int getBit(int x){//获取当前状态的每个位置上是否走过
    int cnt = 0;
    for(int i = 0; i < n; i++){
        bit[i] = x % 2;
        if(bit[i])cnt++;
        x /= 2;
    }
    return cnt;
}
int main(){
    int ans = INF;
    scanf("%d",&n);
    memset(dp,0x3f,sizeof dp);
    for(int i = 0; i < n; i++)
        for(int j = 0; j < n; j++)
            scanf("%d",&G[i][j]);
    for(int i = 1; i < (1 << n); i++){
        int num = getBit(i);
        for(int j = 0; j < n; j++){
            if(num == 1 && bit[0] == 0)break;//如果起点不是0则不合法
            if((bit[j])){
                if(num == 1 && bit[0])dp[j][i] = 0;
                if(dp[j][i] == INF)continue;//说明此时j点没有被转移到,所以不处理
            }
            for(int k = 1; k < n; k++){//由j转移到k
                if(j != k && !bit[k]){
                    int idx = i + (1 << k);
                    dp[k][idx] = min(dp[k][idx],dp[j][i] + G[j][k]);
                    if(k == n - 1 && num == n - 1)ans = min(ans,dp[k][idx]);//只有所有的点走完了,而且终点是n - 1时才更新答案
                }
            }
        }
    }
    printf("%d\n",ans);
    return 0;
}
©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页