loj6274 数字 题解
Posted Little-岸芷汀兰
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了loj6274 数字 题解相关的知识,希望对你有一定的参考价值。
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 数字 题解的主要内容,如果未能解决你的问题,请参考以下文章