转移时就枚举有哪些 [l+1,r−1] 的数在第 b 位被删掉,直接区间 dp 计算贡献就是 O(n3k) 的,但是还有更好的写法。
就是考虑只枚举第一个在第 b 位被删掉,假设是 i,那么 [l,i] 的贡献就是 fb+1,l,i,x,op,但是 [i,r] 的贡献不是 wb,i,r,x,y+fb,i,r,op,y,因为这里 i 第 b 位需要取与 op 对应的界异或 1 的结果,转移就会存在问题。
容易发现可以加一维状态 z 表示 l 这一位需不需要异或 1。这样就能直接转移了,即:fb,l,r,x,y,z←fb+1,l,i,x,op,0+wb,l,i,x,op,z⊕1+fb,i,r,op,y,1,注意这里如果 op 对应 li,则需要满足 li 第 b 位是 0,同理转移 ri 就要这一位是 1。
int n, k; int l[kMaxN], r[kMaxN], c[kMaxN]; int f[kMaxN][kMaxN][kMaxN][2][2][2]; bool vis[kMaxN][kMaxN][kMaxN][2][2][2];
inlinevoidchkmax(int &x, int y){ x = (x > y ? x : y); } inlinevoidchkmin(int &x, int y){ x = (x < y ? x : y); }
intcost(int b, int i, int j, int x, int y, int z){ if (!i || j == n + 1) return0; int v1 = (!x ? l[i] : r[i]) >> b & 1; int v2 = (!y ? l[j] : r[j]) >> b & 1; return (v1 ^ v2 ^ z) * c[b]; }
intdp(int b, int l, int r, int x, int y, int z){ if (vis[b][l][r][x][y][z]) return f[b][l][r][x][y][z]; elseif (b == k) return r - l <= 1 ? 0 : 1e18; vis[b][l][r][x][y][z] = 1; int &ret = f[b][l][r][x][y][z]; ret = 1e18; chkmin(ret, dp(b + 1, l, r, x, y, 0) + cost(b, l, r, x, y, z)); for (int i = l + 1; i <= r - 1; ++i) { if ((::l[i] >> (b + 1)) == (::r[i] >> (b + 1))) continue; for (int op = 0; op < 2; ++op) { if (op == 0 && (::l[i] >> b & 1) || op == 1 && !(::r[i] >> b & 1)) continue; chkmin(ret, dp(b + 1, l, i, x, op, 0) + cost(b, l, i, x, op, z ^ 1) + dp(b, i, r, op, y, 1)); } } return ret; }
voiddickdreamer(){ std::cin >> n >> k; k += 2; for (int i = 1; i <= n; ++i) { std::cin >> l[i] >> r[i]; l[i] = (l[i] << 2), r[i] = (r[i] << 2) | 3; } for (int i = 2; i < k; ++i) std::cin >> c[i]; std::cout << dp(0, 0, n + 1, 0, 0, 0) << '\n'; }