loj6274 数字 题解

Posted Little-岸芷汀兰

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了loj6274 数字 题解相关的知识,希望对你有一定的参考价值。

PPT

loj6274 数字 题解

——DP套DP的神级操作

岸芷汀兰


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

设DP状态 \\(f[i, 0/1,0/1,0/1,0/1]\\) 表示当前考虑了前 \\(i\\) 位,是否存在一组 \\(x\\)\\(y\\),满足 \\(x\\land y=V\\)\\(x\\lor y = T\\)\\(Lx\\leq x\\leq Rx\\)\\(Ly\\leq y\\leq 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\\n", ans);
    return 0;
}

以上是关于loj6274 数字 题解的主要内容,如果未能解决你的问题,请参考以下文章

loj#2076. 「JSOI2016」炸弹攻击 模拟退火

LOJ6276:果树——题解

[LOJ6077][2017 山东一轮集训 Day7]逆序对

LibreOJ 6278. 数列分块入门 2 题解

LOJ6346:线段树:关于时间 ——题解

LOJ132. 树状数组 3 :区间修改,区间查询 题解