2023.3.27

整理了一点状压。

拜托,但是我的状压真的学的和个什么东西一样啊。

我感觉状压 dp 真的很抽象,总结了一下状压 dp 的一些解题要点,以及下面的例题。

1.什么样的题像状态压缩 dp? 一般 n <= 20,处理问题中存在抉择过程,且每种选择都互有影响的问题。

正经来讲就是爆搜,状压也是爆搜的思路,把每一种可能的状态都枚举出来了。这样的题目,用爆搜注意剪枝,疯狂剪就能过。用状压就注意转移的条件。

2.重点:状态转移。注意什么变量的选择对答案有影响?一般用 f[i][j] 表示状态 i 时上一次从 j 转移而来。

3.难点:注意状态转移的条件:怎么才能转移?究竟是包含于当前状态还是不包含?如果有相交还能进行吗?想清楚。

4.细节问题:怎么转移过来,以及最后输出的是什么样的一种问题。

5.关键的小点:建议先写爆搜,弄清楚自己在干啥,把状态设置好,别把自己绕晕了捏!


1.AcWing:91. 最短Hamilton路径

#include<bits/stdc++.h>
using namespace std;
const int N = 21;
int n;
int a[N][N], f[1 << N][N];
int main()
{
    scanf("%d", &n);
    for(int i = 0; i < n; i ++ )
        for(int j = 0; j < n; j ++ )
        {
            scanf("%d", &a[i][j]);
            a[j][i] = a[i][j];
        }
    memset(f, 0x3f, sizeof f);
    f[1][0] = 0;
    for(int i = 2; i < (1 << n); i ++ )
    {
        for(int cur = 1; cur < n; cur ++ )
        {
            if(! (i & (1 << cur))) continue;
            for(int pre = 0; pre < n; pre ++ )
            {
                if(! (i & (1 << pre)) || pre == cur) continue;
                f[i][cur] = min(f[i][cur], f[i - (1 << cur)][pre] + a[pre][cur]);
            }
        }
    }
    printf("%d", f[(1 << n) - 1][n - 1]);
}

用 f[i][j] 两维状态,表示当状态为 i 时上一次选择的第 j 个城市。枚举每一种可能的状态,然后枚举最后的位置,以及上一次是从哪里转移过来的,如果满足条件可以转移(这个城市存在于本次状态),更新。最后答案 f[(1 << n) - 1][n - 1],表示每个城市都走过,从最后一个城市来。


2.AcWing:291. 蒙德里安的梦想

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 12, M = 1 << N;
int n, m;
ll f[N][M];
int st[M];
int main()
{
    while(cin >> n >> m, n || m)
    {
        for(int i = 0; i < 1 << n; i ++ )
        {
            int cnt = 0;
            st[i] = 1;
            for(int j = 0; j < n; j ++ )
            {
                if(i >> j & 1 && cnt & 1) st[i] = 0;
                if(!(i >> j & 1)) cnt ++; 
            }
            if(cnt & 1) st[i] = 0;
        }
        memset(f, 0, sizeof f);
        f[0][0] = 1;
        for(int i = 1; i <= m; i ++ )
            for(int j = 0; j < 1 << n; j ++ )
                for(int k = 0; k < 1 << n; k ++ )
                    if((j & k) == 0 && st[j | k]) f[i][j] += f[i - 1][k];
        printf("%lld\n", f[m][0]);
    }
    return 0;
}

需要考虑的问题:

1.将所有横向长方形放完后,竖向长方形的放置方法有且只有一种,所以只用枚举放置横向长方形的可能性。  

2.满足条件1:摆放完横向长方形后,剩余位置每一行的连续方块个数不能为奇数个(若为奇数个则放竖向长方形会放不开)。实现方法:预处理每一种状态,是否合法。

3.满足条件2:在转移放置的横向长方形时,相邻的两列,横向长方形不能有冲突(比如出现一个方格构成左边一个横向长方形右边一个横向长方形),且此时两种状态相交,总状态合法。

4.最后枚举答案,按照 1 的思路,关注一下这一列和上一列的情况,统计可能放置横向长方形的方案数之和即为总答案。

5.答案 f[m][0] 意为,从 m - 1 行伸展出来,最后一列 m 的状态为 0 的个数。

posted @ 2023-03-27 20:49  Moyyer_suiy  阅读(14)  评论(0编辑  收藏  举报