luoguP5023 NOIP2018 填数游戏 状压dp 找规律

版权声明:_ https://blog.csdn.net/lunch__/article/details/84573556

题意

  • 给你一个 n × m n×m 的矩阵,在每个位置填上 0 0 1 1 ,使得不存在两条路径一条路径先往下走的位置构成的字符串字典序小鱼先后往下走的位置构成的字符串,求方案数。

这个题 N O I P NOIP 的时候找规律拿了 65 p t s 65pts 还是性价比蛮高…

写出正解规律关键在于写出 8 × 8 8×8 的暴力

首先有两个性质

如果 ( x 1 , y ) = ( x , y 1 ) (x-1,y)=(x,y-1) ( x , y ) (x,y) 的右下角所有对角线元素都相同

每条对角线从左下标号为 1 1 的话是单调不上升的

这两个性质可以简单推导证明

那么我们可以写出一个跟对角线有关的状压 d p dp

f [ i ] [ j ] [ S ] f[i][j][S] 为正在处理第 i i 条对角线,上面放了 j j 1 1 ,这条对角线上保证右下角对角线元素都相同的点集为 S S ,那么每次暴力枚举放多少个 1 1 ,转移到的集合 T T ,可以由 S S 集合推出,每次找到每个点右边和下面那个点看能不能满足,直接 d p dp 就可以了,复杂度 O ( 2 n n 3 ( n + m ) ) O(2^nn^3(n+m))

这个暴力可以跑蛮多数据

然后我们可以发现 a n s ( n , m + 1 ) = a n s ( n , m ) 3 ( m > n + 1 ) ans(n,m + 1)=ans(n,m)*3(m>n+1)

那么我们在 m = n m=n 的时候跑dp算

否则就跑 ( n , n + 1 ) (n,n+1) 的dp 再乘上 3 m n 1 3^{m - n - 1} 就可以了

Codes

#include <bits/stdc++.h>

using namespace std;

const int mod = 1e9 + 7;

int f[18][9][1 << 8], len[18], n, m, ans = 1;

bool inset(int x, int S) {return (1 << (x - 1)) & S;}
int down(int x, int y) {return y - (x <= m);}
int right(int x, int y) {return y + (x > m);}

int DP() {
    int num = n + m - 1; 
    for (int i = 1; i <= m; ++ i) 
        len[i] = min(i, n);
    for (int i = 1; i < n; ++ i) 
        len[i + m] = n - i; 

    f[1][0][1] = f[1][1][1] = 1; 
    for (int i = 2; i <= num; ++ i) {
        int all = 1 << len[i - 1];
        for (int j = 0; j <= len[i - 1]; ++ j) 
            for (int S = 0; S < all; ++ S) 
                if (f[i - 1][j][S]) for (int k = 0; k <= len[i]; ++ k) {
                    int flag = true, T = 0;
                    for (int r = 1; r < len[i]; ++ r) 
                        if (r != k && !inset(right(i, r), S)) {
                            flag = false; break;
                        }
                    if (flag) {
                        for (int r = 1; r <= len[i]; ++ r) {
                            int D = down(i, r), R = right(i, r);
                            if (!D && inset(R, S)) T |= 1 << (r - 1);
                            else if (R > len[i - 1] && inset(D, S)) T |= 1 << (r - 1);
                            else if (r != j && inset(D, S) && inset(R, S)) T |= 1 << (r - 1);
                        }
                        (f[i][k][T] += f[i - 1][j][S]) %= mod; 
                    }
                }
    }
    return ((f[num][0][1] + f[num][1][1]) % mod + (f[num][0][0] + f[num][1][0]) % mod) % mod; 
}

int main() {
#ifdef ylsakioi
    freopen("5023.in", "r", stdin);
    freopen("5023.out", "w", stdout);
#endif

    scanf("%d%d", &n, &m); 
    if (n > m) swap(n, m);
    if (n == m || m <= 8) ans = DP();
    else {
        for (; m > n + 1; -- m) 
            ans = 3ll * ans % mod; 
        ans = 1ll * ans * DP() % mod; 
    }
    printf("%d\n", ans);

    return 0;
}

猜你喜欢

转载自blog.csdn.net/lunch__/article/details/84573556