loj6274 数字 题解

PPT

loj6274 数字 题解

——DP套DP的神级操作

岸芷汀兰


首先考虑朴素做法。我们枚举 (V),检查是否能存在 (x)(y) 满足 (xland y=V)

设DP状态 (f[i, 0/1,0/1,0/1,0/1]) 表示当前考虑了前 (i) 位,是否存在一组 (x)(y),满足 (xland y=V)(xlor y = T)(Lxleq xleq Rx)(Lyleq yleq Ry)。后面那四组 (0/1) 表示 (x)(y) 是否贴着上下界。

相信各位同学也都能看出来,这就是一个简单的数位DP!

从高位向低位转移即可。


代码片段:

inline bool solve() {
    mem(f, 0);
    f[11][1][1][1][1] = 1;
    for (int i = 11; i >= 2; -- i) { 
        for (int lx = 0; lx <= 1; ++ lx) {// 第i位
            for (int rx = 0; rx <= 1; ++ rx) {
                for (int ly = 0; ly <= 1; ++ ly) {
                    for (int ry = 0; ry <= 1; ++ ry) {
                        for (int x = 0; x <= 1; ++ x) { // 第i-1位
                            for (int y = 0; y <= 1; ++ y) {
                                if (!f[i][lx][rx][ly][ry]) continue;

                                if (lx == 1 && Lx[i - 1] == 1 && x == 0) continue;
                                if (rx == 1 && Rx[i - 1] == 0 && x == 1) continue;
                                if (ly == 1 && Ly[i - 1] == 1 && y == 0) continue;
                                if (ry == 1 && Ry[i - 1] == 0 && y == 1) continue;

                                if ((x | y) != T[i - 1]) continue;
                                if ((x & y) != V[i - 1]) continue;

                                f[i - 1][lx & (x == Lx[i - 1])][rx & (x == Rx[i - 1])][ly & (y == Ly[i - 1])][ry & (y == Ry[i - 1])] = 1;
                            }
                        }
                    }
                }
            }
        }
    }
    for (int lx = 0; lx <= 1; ++ lx) {
        for (int rx = 0; rx <= 1; ++ rx) {
            for (int ly = 0; ly <= 1; ++ ly) {
                for (int ry = 0; ry <= 1; ++ ry) {
                    if (f[1][lx][rx][ly][ry]) return true;
                }
            }
        }
    }
    return false;
}

现在考虑如何优化这个算法。

我们发现每次枚举 (V) 的过程太耗时。于是这启发我们思考能不能直接计算出合法 (V) 的数量。

对于一个确定的 (V) 来说,(f) 数组肯定是确定的。但如果反过来,一套确定的 (f) 数组可不一定对应着唯一的 (V)。所以,如果我们能对于某一套 (f) 数组,直接算出有多少个 (V) 会算出这一套 (f) 并累加到答案中,计算效率就会提高不少。


现在就要祭出大法:DP套DP!

设外层DP状态 (F[i,S]) 表示当前考虑了前 (i) 位,(f[i]) 数组的“情况”是 (S),所对应的 (V) 的数量。

其中 (S) 是一个16位二进制数。第一位代表 (f[i,0,0,0,0]) 的值,第二位代表 (f[i,0,0,0,1]) 的值,第三位代表 (f[i,0,0,1,0]) 的值……以此类推。

怎么转移呢?

首先枚举位数 (i)。其次枚举 (S)。再其次枚举 (V) 的第 (i) 位是0还是1。然后用我们刚才提出的小 (f) 的转移方法,用 (S) 能转移出 (P) 来。最后把 (F[i,S]) 累加到 (f[i-1,P]) 中即可。

具体实现见代码。这份代码不开O3过不去,原因是我每次直接把 (f) 数组求出来了,如果直接使用位运算会快很多。



#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;
#define FILEIN(s) freopen(s".in", "r", stdin);
#define FILEOUT(s) freopen(s".out", "w", stdout)
#define mem(s, v) memset(s, v, sizeof s)

inline long long read(void) {
    long long x = 0, f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return f * x;
}

const int maxn = 63;

bool f[2][2][2][2], g[2][2][2][2];
char T[maxn], Lx[maxn], Rx[maxn], Ly[maxn], Ry[maxn];
long long F[maxn][70000];

inline void change(long long x, char str[]) {
    int tot = 0;
    while (x) {
        str[++ tot] = x % 2;
        x /= 2;
    }
}

inline int call(int s, int i) { return (s >> i) & 1; }

inline int get(int i, int st, int v) {
    mem(g, 0);
    int cnt = 0;
    for (int lx = 0; lx <= 1; ++ lx) { // 第i位
        for (int rx = 0; rx <= 1; ++ rx) {
            for (int ly = 0; ly <= 1; ++ ly) {
                for (int ry = 0; ry <= 1; ++ ry) {
                    f[lx][rx][ly][ry] = call(st, cnt ++);
                    if (!f[lx][rx][ly][ry]) continue;

                    for (int x = 0; x <= 1; ++ x) { // 第i-1位
                        for (int y = 0; y <= 1; ++ y) {
                            if (lx == 1 && Lx[i - 1] == 1 && x == 0) continue;
                            if (rx == 1 && Rx[i - 1] == 0 && x == 1) continue;

                            if (ly == 1 && Ly[i - 1] == 1 && y == 0) continue;
                            if (ry == 1 && Ry[i - 1] == 0 && y == 1) continue;

                            if ((x | y) != T[i - 1]) continue;
                            if ((x & y) != v) continue;

                            g[lx & (x == Lx[i - 1])][rx & (x == Rx[i - 1])][ly & (y == Ly[i - 1])][ry & (y == Ry[i - 1])] = 1;
                        }
                    }
                }
            }
        }
    }
    cnt = 0;
    int res = 0;
    for (int lx = 0; lx <= 1; ++ lx) {
        for (int rx = 0; rx <= 1; ++ rx) {
            for (int ly = 0; ly <= 1; ++ ly) {
                for (int ry = 0; ry <= 1; ++ ry) {
                    res |= (g[lx][rx][ly][ry] << cnt);
                    ++ cnt;
                }
            }
        }
    }
    return res;
}

int main() {
    change(read(), T); 
    change(read(), Lx); change(read(), Rx);
    change(read(), Ly); change(read(), Ry);
    F[61][1 << 15] = 1;
    for (int i = 61; i >= 2; -- i) {
        for (int st = 0; st < (1 << 16); ++ st) {
            for (int v = 0; v <= 1; ++ v) { // V[i - 1]
                int to = get(i, st, v);
                F[i - 1][to] += F[i][st];
            }
        }
    }
    long long ans = 0;
    for (int s = 1; s < (1 << 16); ++ s) ans += F[1][s];
    printf("%lld
", ans);
    return 0;
}
原文地址:https://www.cnblogs.com/little-aztl/p/14802316.html