DP of DP 学习笔记 ?
前言
其实 DP 套 DP 可能套的并不是 DP。
这类问题大概都是形如:定义满足条件 \(A\) 的 \(B\) 是合法的,求某些限制下合法 \(B\) 的数量。
DP 套 DP 可以分成很多种类型,我大概是根据内层维护的方式去分。
内层构造自动机
大概是通过构造 DFA/NFA,然后在 DFA/NFA 上面跑 DP。这样我们就只需要在意当前在哪个点上而不需要在意整个状态了。
P4590 [TJOI2018] 游园会
如果我们已经构造出一个 DFA,怎么在这上面跑 DP。
先考虑 DFA 上的点应该能够表示什么。因为我们相当于是拿一个已知的串在这个 DFA 上走,所以走完之后应该是能够知道这个串和原串的 \(\operatorname{LCS}\) 的。那么 DFA 上每个点需要维护如果一个串在当前点结束,那么这个串和原串的 \(\operatorname{LCS}\) 应该是多少。不妨设 \(l_s\) 表示 DFA 上 \(s\) 这个点对应的 \(\operatorname{LCS}\) 值。
定义状态函数 \(f_{i,s,j}\) 表示已经走完前 \(i\) 个字符,当前点在 \(s\),且最后和 NOI 的匹配长度为 \(j\) 的方案数。考虑转移:
- 加入
N。\(f_{i,c_{s,N},1}\gets f_{i-1,s,0/1/2}\)。 - 加入
O。\(f_{i,c_{s,O},2}\gets f_{i-1,s,1},f_{i,c_{s,O},0}\gets f_{i-1,s,0/2}\)。 - 加入
I。\(f_{i,c_{s,O},0}\gets f_{i-1,s,0/1}\)。
其中 \(c_{u,x}\) 表示 DFA 上 \(u\) 这个点走 \(x\) 这条边到达的点的编号。
则最后 \(ans_k = \sum f_{n,s,0/1/2}[l_s = k]\)。这部分时间复杂度 \(O(nm)\),其中 \(m\) 为 DFA 中点的数量。
考虑构造这样一个 DFA。因为我们要知道 \(\operatorname{LCS}\),所以尝试根据求 \(\operatorname{LCS}\) 的方式来构造。定义 \(dp_{i,j}\) 表示第一个串前 \(i\) 个和第二个串前 \(j\) 个的 \(\operatorname{LCS}\)。那么:
如果我们把 \(s\) 看成初始给定的那个字符串,\(t\) 看成我们放 DFA 上跑的那个字符串。那么我们关心的是 \(j \to j+1\) 时 \(dp_{*}\) 会变成多少。发现只要我们 DFA 上每个点维护出了某个 \(dp_{*}\) 的状态,那么加入一个字符 \(x\) 后 \(dp_{*}\) 的状态是可以快速得到的。相当于我们把当前点的 \(dp_{*}\) 状态记为 \(g\),加入字符 \(x\) 后到的点的 \(dp_*\) 状态记为 \(h\)。那么对于 \(h_i\),应该满足:\(h_i = \max(h_{i-1},g_{i},g_{i-1}+[s_i = x])\)。也就是说,只要每个点能够维护出这个点状态对应的 \(dp_*\),就可以快速得到 \(\operatorname{LCS}\)。
显然,我们不能直接维护。因为 \(dp_i \in [0,k]\),暴力存的话状态数是 \(O((k+1)^k)\) 的。注意到 \(dp_{i+1}\in \{dp_i,dp_i+1\}\),也就是差分数组将会是一个二进制数。如果我们不存 \(dp_{*}\) 而是存 \(dp_*\) 的差分数组 \(d_*\),状态数就是 \(O(2^k)\) 的了。
那么 DFA 上每个点维护一个长度为 \(k\) 的二进制数,表示这个状态 \(dp_{*}\) 的差分数组。枚举加入的字符 \(x\),先还原出 \(g_{*}\),再求出 \(h_*\),再转成差分数组 \(d*\),就可以得到 \(s\) 这个点加入字符 \(x\) 后会到达哪个点了。这样总状态数 \(O(2^k)\),建 DFA 的时间复杂度 \(O(k2^k)\)。则总时间复杂度 \(O((n+k)2^k)\)。
【代码】
const int N = 1005, M = 16, mod = 1e9 + 7;
int n, k;
char S[N];
int g[M], h[M];
int c[1 << M][3];
int f[2][1 << M][3];复杂度
char cc[3] = {'N', 'O', 'I'};
int res[M];
il void build(){
UU(s, 0, (1 << k)){
U(i, 1, k){
g[i] = g[i - 1];
if((s >> (i - 1)) & 1) ++ g[i];
}
U(x, 0, 2){
U(i, 1, k){
h[i] = max(h[i - 1], g[i]);
if(cc[x] == S[i]) h[i] = max(h[i], g[i - 1] + 1);
}
int t = 0;
U(i, 1, k){
t += (1 << (i - 1)) * (h[i] - h[i - 1]);
}
c[s][x] = t;
}
}
return ;
}
il void solve(){
n = rd, k = rd;
scanf("%s", S + 1);
build(); int w = 0;
f[w][0][0] = 1;
U(i, 1, n){
w ^= 1;
memset(&f[w], 0, sizeof(f[w]));
UU(s, 0, (1 << k)){
f[w][c[s][0]][1] = (f[w][c[s][0]][1] + f[w ^ 1][s][0] + f[w ^ 1][s][1] + f[w ^ 1][s][2]) % mod;
f[w][c[s][1]][2] = (f[w][c[s][1]][2] + f[w ^ 1][s][1]) % mod;
f[w][c[s][1]][0] = (f[w][c[s][1]][0] + f[w ^ 1][s][0] + f[w ^ 1][s][2]) % mod;
f[w][c[s][2]][0] = (f[w][c[s][2]][0] + f[w ^ 1][s][0] + f[w ^ 1][s][1]) % mod;
}
}
UU(s, 0, (1 << k)) U(i, 0, 2){
res[__builtin_popcountll(s)] = (res[__builtin_popcountll(s)] + f[w][s][i]) % mod;
}
U(i, 0, k) cout << res[i] << "\n";
return ;
}
CF924F Minimal Subset Difference
逆天。
这个看起来就大概率不能贪心。考虑暴力地 DP 维护。定义 \(f_{i,x}\) 表示前 \(i\) 个数字,当前值为 \(x\) 的可行性。考虑加入一个数字 \(c\):
- \(f_{i,x + c} = f_{i,x+c}| f_{i-1,x}\)
- \(f_{i,|x-c|}=f_{i,|x-c|}|f_{i-1,x}\)
最后这个数是 \(k\) 美丽的,当仅当存在 \(0 \le x \le k\),使得 \(f_{x} =1\)。由于在 \(n \le 10^{18}\) 时,最劣的构造是 \(999999998888888888\),此时 \(f\) 的第二维是要维护到 \(9\times 8 = 72\) 的。所以 DP 一次的时间复杂度 \(O(\alpha \log_{10}n)\),其中 \(\alpha = 72\)。
一个比较显然的想法是我们直接去数位 DP,同时维护当前 \(f_i\) 有哪些是 \(1\)。但这样状态数是 \(O(\log_{10}n 2^{\alpha}),\alpha = 72\) 的,无法接受。
根据注意力,可以感知到实际上 \(f_{i,*}\) 的状态并不多。考虑给 \(f\) 搞个 DFA,这样 DP 的时间复杂度是 \(O(k \log_{10}n m),k=10\) 的,其中 \(m\) 是 DFA 上点数,也就是可能的 \(f\) 状态数。
跑一个暴力构建 DFA 发现点数确实不多,然后就做完了。这个状态只有 \(12879\) 个。
这启示我们在无从下手的时候,可以尝试只对有用状态进行保留。
内层维护贪心
通常判定一个状态 \(B\) 是否满足条件 \(A\) 可以尝试贪心,并且一般贪心所需维护的内容并不多。遂可以在外面套层 DP 做。
AT_agc022_e [AGC022E] Median Replace
先考虑判定一个串 \(S\) 是否是合法的。贪心地想,只要我们尽可能保留 \(1\),删掉 \(0\) 就行了。顺着扫过去,维护一个栈:
- 加入一个 \(0\)。
- 最后两个是 \(00\)。此时形成 \(000\),将其变成 \(0\) 不劣。
- 最后两个不是 \(00\)。先加进来,因为后面可能是 \(0\),此时可能变成情况 \(1.1\)。
- 加入一个 \(1\)。
- 最后一个是 \(0\)。因为删掉 \(01\) 对 \(0,1\) 数量影响不变,且如果最后剩下一个 \(1\) 这一对 \(01\) 中的 \(0\) 必须带走一个 \(1\),\(1\) 必须带走一个 \(0\)。所以删掉 \(01\) 不劣。
- 最后一个是 \(1\)。加进来不管。
显然,任意时刻栈中元素都是形如 $111\dots 11 0 0 $,其中 \(0\) 的数量不超过 \(2\)。那么最后 \(S\) 合法,当且仅当最后复杂度 \(0\) 的数量小于 \(1\) 的数量。
那么尝试对这玩意 DP。定义状态函数 \(f_{i,x,y}\) 表示前 \(i\) 个字符,当前栈是 \(x\) 个 \(1\) 后面加上 \(y\) 个 \(0\) 的结构的方案数。那么:
- \(S_i = 0\)。\(f_{i,x,1} \gets f_{i-1,x,0/2},f_{i,x,2}\gets f_{i-1,x,1}\)。
- \(S_i = 1\)。\(f_{i,x+1,0}\gets f_{i-1,x,0},f_{i,x,1}\gets f_{i-1,x,2},f_{i,x,0}\gets f_{i-1,x,1}\)。
最后答案为 \(\sum f_{n,x,y} [x > y]\)。时间复杂度 \(O(n^2)\)。
发现当某个时刻 \(x = 3\),无论后面的字符串长什么样都是一定合法的。所以实际上我们只需要维护 \(\min(x,3)\)。时间复杂度 \(O(n)\)。
【代码】
const int N = 1e6 + 10, mod = 1e9 + 7;
int n;
char s[N];
int f[2][4][4];
il void solve(){
scanf("%s", s + 1);
n = strlen(s + 1);
int w = 0;
f[w][0][0] = 1;
U(i, 1, n){
w ^= 1;
memset(&f[w], 0, sizeof(f[w]));
if(s[i] != '1'){
U(x, 0, 3){
f[w][x][1] = (f[w][x][1] + f[w ^ 1][x][0] + f[w ^ 1][x][2]) % mod;
f[w][x][2] = (f[w][x][2] + f[w ^ 1][x][1]) % mod;
}
}
if(s[i] != '0'){
U(x, 0, 3){
f[w][min(x + 1, 3ll)][0] = (f[w][min(x + 1, 3ll)][0] + f[w ^ 1][x][0]) % mod;
f[w][x][1] = (f[w][x][1] + f[w ^ 1][x][2]) % mod;
f[w][x][0] = (f[w][x][0] + f[w ^ 1][x][1]) % mod;
}
}
}
int res = 0;
U(x, 0, 3) UU(y, 0, x) res = (res + f[w][x][y]) % mod;
cout << res << "\n";
return ;
}
内层维护 DP
CF1142D Foreigner
考虑一个排名为 \(x\) 的数添加字符 \(y\) 后得到的数字排名。首先对于排名为 \([1,x -1]\) 中所有数,再其后面添加一个小于它的排名对 \(11\) 取模的字符都比排名为 \(x\) 的数字后面添加字符 \(y\) 小。排名为 \(x\) 的数后面添加一个比 \(y\) 小的字符也比它小。那么一个排名为 \(x\) 的数,添加字符 \(y\) 后得到的数字的排名应该是 \((\sum\limits_{i=1}^{x-1}i\bmod 11) + (y + 1) + 9\)。条件是 \(y < x \bmod 11\)。
那么判定一个字符串 \(S\) 合法,就只需要顺着遍历过去,维护前缀的排名。
现在考虑对 \(s\) 求合法子串数量。因为:
所以我们只需要维护当前子串排名对 \(11\) 取模的结果。定义 \(f_{i,j}\) 表示所有以 \(i\) 结尾的子串,排名对 \(11\) 取模为 \(j\) 的数量。考虑 $i -1\to i $ 的情况:
- 字符串是 \(s_{i}\)。\(f_{i,s_i}\gets 1[s_i > 0]\)。
- 字符串是 \(s_i\) 往前数若干个字符。\(f_{i,g(x,s_i)}\gets f_{i-1,x} [x> s_i]\)。其中 \(g(x,y)\) 为上述式子的值。
最后答案就是 \(\sum f\),时间复杂度 \(O(n\alpha),\alpha = 11\)。
P8352 [SDOI/SXOI2022] 小 N 的独立集
先考虑最大权独立集怎么求。有:
一个暴力的思路是,直接去维护 \(f_{*,0/1}\)。也就是定义 \(dp_{u,x,y}\) 表示 \(u\) 为根子树,\(f_{u,0} = x, f_{u,1}=y\) 的方案数。这样时间复杂度 \(O(n^4)\)。
注意到 \(val_u \le 5\),尝试从这个角度入手。先令 \(f_{u,1} = \max(f'_{u,0},f'_{u,1})\),也就是将题目限制变成:
记 \(\delta_u = f_{u,1} - f_{u,0}\)。那么转移方程又可以变成:
发现此时 \(f_{u,0}\) 是没有用的,并且 \(\delta_u \le val_u \le 5\)。维护 \(dp_{u,x,y}\) 表示 \(u\) 为根子树,\(f_{u,1} = x, \delta_u = y\) 的方案数。
那么先跑一遍 \(g_{x,y}\) 表示 \(u\) 的儿子中 \(\sum f_{v,1} = x, \sum \delta_v =y\) 的方案数,有:
然后枚举 \(val_u\),更新到 \(dp_{u,*,*}\)。这样时间复杂度 \(O(n^2 k^4)\)。这里保证复杂度的原因是我们只需要维护 \(y = \min(k, y')\),则 \(g\) 的第二维不可能超过 \(5\)。
USACO 2022.2 Platinum Phone Numbers
作者认为这题毫无意义,所以题解可能有点莫名其妙,因为是草稿。
浪费 2.5h。
考虑先判定一个电话号码是否合法。
定义状态函数 \(f_{i}\) 表示能否凑完 \(t_{1,\dots, i}\)。那么有转移方程:
时间复杂度 \(O(n)\)。注意到我们只在意 \(i-1,i-2,i-4\) 的 \(f\) 值,所以考虑定义状态函数 \(dp_{i,s,a,b,c}\) 表示前 \(i\) 个数,\(f_{i-4\sim i-1}\) 的状态是 \(s\),\(i-3,i-2,i-1\) 的 \(t\) 值分别是 \(a,b,c\) 的方案数。这样时间复杂度 \(O(n2^49^3)\)。
考虑优化状态。
注意到如果 \(f_{i-4} = 0\),此时记录 \(t_{i-3}\) 是没用的。如果同时 \(f_{i-3}=0\),记录 \(t_{i-2}\) 也是没用的。如果 \(s_{i-3,i-2,i-1}\) 不构成三角形,记录 \(t_{i-3}\) 也是没用的。然后这个状态数就很小了。时间复杂度未知。
神秘代码:
const int N = 1e5 + 10, mod = 1e9 + 7;
int n;
char s[N];
int f[2][2][2][2][2][12][10][10];
int chk[10][10], h[10][10][10];
struct node{
int x, y, z, w, a, b, c;
}st[2][N];
int top[2];
il bool c1(int x, int y){
return (x == y);
}
il bool c2(int a, int b, int c, int d){
if(a > b) swap(a, b);
if(c > d) swap(c, d);
return (chk[a][b] && a == c && b == d);
}
il int g(int a, int b, int c){
if(a > b) swap(a, b);
if(b > c) swap(b, c);
if(a > b) swap(a, b);
if(b > c) swap(b, c);
if(a > b) swap(a, b);
return h[a][b][c];
}
il void init(){
memset(chk, 0, sizeof(chk));
memset(h, -1, sizeof(h));
chk[1][2] = chk[2][3] = chk[4][5] = chk[5][6] = chk[7][8] = chk[8][9] = 1;
chk[1][4] = chk[4][7] = chk[2][5] = chk[5][8] = chk[3][6] = chk[6][9] = 1;
{
int a = 1, b = 2, c = 4, d = 5;
h[a][b][c] = d;
h[a][c][d] = b;
h[b][c][d] = a;
h[a][b][d] = c;
}
{
int a = 2, b = 3, c = 5, d = 6;
h[a][b][c] = d;
h[a][c][d] = b;
h[b][c][d] = a;
h[a][b][d] = c;
}
{
int a = 4, b = 5, c = 7, d = 8;
h[a][b][c] = d;
h[a][c][d] = b;
h[b][c][d] = a;
h[a][b][d] = c;
}
{
int a = 5, b = 6, c = 8, d = 9;
h[a][b][c] = d;
h[a][c][d] = b;
h[b][c][d] = a;
h[a][b][d] = c;
}
return ;
}
il bool Chk(int a, int b, int c, int d, int aa, int bb, int cc, int dd){
return ((a + 1000 + 2*a) * (b + 1000+ 2*b) * (c + 1000+2*c) * (d + 1000+2*d))
== ((aa + 1000+2*aa) * (bb + 1000+2*bb) * (cc + 1000+2*cc) * (dd + 1000+2*dd));
}
il void solve(){
scanf("%s", s + 1); n = strlen(s + 1);
memset(f, 0, sizeof(f));
f[0][0][0][0][1][0][0][1] = 1;
f[0][0][0][0][1][10][0][1] = 1;
top[0] = top[1] = 0;
st[0][++top[0]] = {0, 0, 0, 1, 0, 0, 1};
st[0][++top[0]] = {0, 0, 0, 1, 10, 0, 1};
int I = 0;
U(i, 1, n){
I ^= 1;
U(j, 1, top[I]){
auto [x, y, z, w, a, b, c] = st[I][j];
f[I][x][y][z][w][a][b][c] = 0;
}
top[I] = 0;
U(x, 0, 1) U(y, 0, 1) U(z, 0, 1) U(w, 0, 1){
if(x == 0 && y == 0){
U(a, 0, 0) U(b, 0, 0) U(c, 1, 9) if(f[I ^ 1][x][y][z][w][a][b][c]) U(d, 1, 9){
int aa = a, bb = b, cc = c, dd = d;
bool f2 = y;
if(y == 1 && g(b, c, d) == -1){
bb = 0;
f2 = 0;
if(z == 0) cc = 0;
}
else{
if(y == 0) bb = 0;
if(y == 0 && z == 0) cc = 0;
}
bool flg = 0;
if(w && c1(s[i] - '0', d)) flg = 1;
else if(z && c2(s[i - 1] - '0', s[i] - '0', c, d)) flg = 1;
f[I][f2][z][w][flg][bb][cc][dd] = (f[I][f2][z][w][flg][bb][cc][dd] + f[I ^ 1][x][y][z][w][a][b][c]) % mod;
f[I][f2][z][w][flg][10][cc][dd] = (f[I][f2][z][w][flg][10][cc][dd] + f[I ^ 1][x][y][z][w][a][b][c]) % mod;
st[I][++top[I]] = {f2,z,w,flg,bb,cc,dd};
st[I][++top[I]] = {f2,z,w,flg,10,cc,dd};
}
}
else if(x == 0){
U(b, 1, 9) U(c, 1, 9) if(f[I ^ 1][x][y][z][w][10][b][c]) U(d, 1, 9){
int bb = b, cc = c, dd = d;
bool f2 = y;
if(y == 1 && g(b, c, d) == -1){
bb = 0;
f2 = 0;
if(z == 0) cc = 0;
}
else{
if(y == 0) bb = 0;
if(y == 0 && z == 0) cc = 0;
}
bool flg = 0;
if(w && c1(s[i] - '0', d)) flg = 1;
else if(z && c2(s[i - 1] - '0', s[i] - '0', c, d)) flg = 1;
if(flg == 1){
f[I][f2][z][w][flg][bb][cc][dd] = (f[I][f2][z][w][flg][bb][cc][dd] + f[I ^ 1][x][y][z][w][10][b][c]) % mod;
f[I][f2][z][w][flg][10][cc][dd] = (f[I][f2][z][w][flg][10][cc][dd] + f[I ^ 1][x][y][z][w][10][b][c]) % mod;
st[I][++top[I]] = {f2,z,w,flg,bb,cc,dd};
st[I][++top[I]] = {f2,z,w,flg,10,cc,dd};
}
else{
f[I][f2][z][w][0][bb][cc][dd] = (f[I][f2][z][w][0][bb][cc][dd] + f[I ^ 1][x][y][z][w][10][b][c]) % mod;
f[I][f2][z][w][0][10][cc][dd] = (f[I][f2][z][w][0][10][cc][dd] + f[I ^ 1][x][y][z][w][10][b][c]) % mod;
st[I][++top[I]] = {f2,z,w,0,bb,cc,dd};
st[I][++top[I]] = {f2,z,w,0,10,cc,dd};
}
}
}
else{
U(b, 1, 9) U(c, 1, 9) if(f[I ^ 1][x][y][z][w][10][b][c]) U(d, 1, 9){
int bb = b, cc = c, dd = d;
bool flg = 0;
bool f2 = y;
if(y == 1 && g(b, c, d) == -1){
bb = 0;
f2 = 0;
if(z == 0) cc = 0;
}
else{
if(y == 0) bb = 0;
if(y == 0 && z == 0) cc = 0;
}
if(w && c1(s[i] - '0', d)) flg = 1;
else if(z && c2(s[i - 1] - '0', s[i] - '0', c, d)) flg = 1;
if(flg == 1){
f[I][f2][z][w][flg][bb][cc][dd] = (f[I][f2][z][w][flg][bb][cc][dd] + f[I ^ 1][x][y][z][w][10][b][c]) % mod;
f[I][f2][z][w][flg][10][cc][dd] = (f[I][f2][z][w][flg][10][cc][dd] + f[I ^ 1][x][y][z][w][10][b][c]) % mod;
st[I][++top[I]] = {f2,z,w,flg,bb,cc,dd};
st[I][++top[I]] = {f2,z,w,flg,10,cc,dd};
}
else{
int a = g(b, c, d);
if(i >= 4 && Chk(s[i - 3] - '0', s[i - 2] - '0', s[i - 1] - '0', s[i] - '0', a, b, c, d) && a != -1){
f[I][f2][z][w][1][bb][cc][dd] = (f[I][f2][z][w][1][bb][cc][dd] + f[I ^ 1][x][y][z][w][a][b][c]) % mod;
f[I][f2][z][w][0][bb][cc][dd] = (f[I][f2][z][w][0][bb][cc][dd] + f[I ^ 1][x][y][z][w][10][b][c] - f[I ^ 1][x][y][z][w][a][b][c] + mod) % mod;
f[I][f2][z][w][1][10][cc][dd] = (f[I][f2][z][w][1][10][cc][dd] + f[I ^ 1][x][y][z][w][a][b][c]) % mod;
f[I][f2][z][w][0][10][cc][dd] = (f[I][f2][z][w][0][10][cc][dd] + f[I ^ 1][x][y][z][w][10][b][c] - f[I ^ 1][x][y][z][w][a][b][c] + mod) % mod;
st[I][++top[I]] = {f2,z,w,0,bb,cc,dd};
st[I][++top[I]] = {f2,z,w,0,10,cc,dd};
st[I][++top[I]] = {f2,z,w,1,bb,cc,dd};
st[I][++top[I]] = {f2,z,w,1,10,cc,dd};
}
else{
f[I][f2][z][w][0][bb][cc][dd] = (f[I][f2][z][w][0][bb][cc][dd] + f[I ^ 1][x][y][z][w][10][b][c]) % mod;
f[I][f2][z][w][0][10][cc][dd] = (f[I][f2][z][w][0][10][cc][dd] + f[I ^ 1][x][y][z][w][10][b][c]) % mod;
st[I][++top[I]] = {f2,z,w,0,bb,cc,dd};
st[I][++top[I]] = {f2,z,w,0,10,cc,dd};
}
}
}
}
}
}
int res = 0;
U(x, 0, 1) U(y, 0, 1) U(z, 0, 1) U(w, 1, 1) U(a, 0, 9) U(b, 0, 9) U(c, 0, 9){
res = (res + f[I][x][y][z][w][a][b][c]) % mod;
} cout << res << "\n";
return ;
}
AT_arc193_b [ARC193B] Broken Wheel
首先如果 \(d_{1\dots n-1}\) 确定了,\(d_{n}\) 也是确定的。则问题变成,给环定向,并且每个 \(s_i = 1\) 的位置可以额外获得最多一个入边,求不同 \(d\) 的数量。
现在考虑对一种 \(d\) 的序列判定合法性。
维护 \(f_{i,0/1,0/1}\) 表示匹配完 \([1,i]\),\((i,i+1)\) 的边向左或向右且 \((n,1)\) 的边指向 \(n\) 或 \(1\) 的可行性。
-
\(s_i = 0\)。
\[f_{i,0,k}= (f_{i-1,0,k}[d_i \in\{ 1\}]) |(f_{i-1,1,k}[d_i\in\{ 2\}])\\ f_{i,1,k} = (f_{i-1,0,k}[d_i\in\{ 0\}])|(f_{i-1,1,k}[d_i\in\{ 1\}]) \] -
\(s_i = 1\)。
\[f_{i,0,k}= (f_{i-1,0,k}[d_i \in \{1,2\}]) |(f_{i-1,1,k}[d_i\in \{2,3\}])\\ f_{i,1,k} = (f_{i-1,0,k}[d_i\in \{0,1\}])|(f_{i-1,1,k}[d_i \in \{1,2\}]) \]
对于 \(i=1\),有:
-
\(s_1 = 0\)。
\[f_{1,0,0} = [d_1 \in\{1\}]\\ f_{1,1,0} = [d_1\in\{0\}]\\ f_{1,0,1} =[d_1\in\{2\}]\\ f_{1,1,1}=[d_1\in\{1\}] \] -
\(s_1 = 1\)。
\[f_{1,0,0} = [d_1 \in\{1,2\}]\\ f_{1,1,0} = [d_1\in\{0,1\}]\\ f_{1,0,1} =[d_1\in\{2,3\}]\\ f_{1,1,1}=[d_1\in\{1,2\}] \]最后当 \(f_{n,0,0}\) 或 \(f_{n,1,1}\) 是 \(1\) 时合法。
那么考虑对这个 DP 过程计数,定义 \(dp_{i,a,b,c,d}\) 表示前 \(i\) 个点,\(f_{i,0,0}=a,f_{i,1,0}=b,f_{i,0,1}=c,f_{i,1,1}=d\) 的方案数。那么枚举 \(d_i = x\),就可以根据 \(f\) 的转移来转移了。时间复杂度 \(O(n2^k),k=6\)。
P15301 [ROI 2012 Day 2] army 汗国军队
考虑按照值从小到大插入序列,维护 \(f_i\) 表示当前位置 \(i\) 这个点如果是 \(\operatorname{LIS}\) 的末尾,此时 \(\operatorname{LIS}\) 的值是多少。
那么初始时 \(f_{i} = 0\)。将 \(x\) 这个值插入序列,若它插在位置 \(pos\)。那么有转移方程:\(f_{pos} = \max\limits_{j=0}^{pos-1}f_{j}+1\)。那么可以维护 \(g_{i} = \max\limits_{j=0}^{i}f_j\),则对于 \(i\in [pos,n]\) 有 \(g_{i}\gets \max(g_{i},g_{pos-1}+1)\)。
这样有个比较好的性质:\(g_i \in \{g_{i-1},g_{i-1}+1\}\)。那么再搞个差分数组就可以用一个二进制数维护 \(g\) 数组了。
因为题目要求 \(k\) 个数是某个 \(LIS\),考虑定义 \(dp_{s,lst,t}\) 表示加入完 \(1\sim \operatorname{popcount}(s)\) 这些值,最后一个属于题目给定的那 \(k\) 个数的值的位置在 \(lst\),且 \(g\) 数组的差分是 \(t\) 的方案数。
考虑转移,枚举 \(s,t\)。这个的时间复杂度是 \(O(3^n)\) 的,因为 \(t\) 中 \(1\) 的位置在 \(s\) 中也一定是 \(1\)(不可能这个位置还没有数但是 \(f\) 值比上一个大)。那么每个位置在 \(s,t\) 中的情况只会是 \(00,10,11\) 三种。再枚举当前这个值的位置 \(pos\),可以 \(O(n)\) 得到 \(s',t'\)。一共是 \(O(n^23^n)\) 的时间复杂度。然后再对每个 \(pos\) 枚举 \(lst\) 进行转移,时间复杂度依旧 \(O(n^23^n)\)。
那么总时间复杂度就是 \(O(n^23^n)\) 的,答案是 \(\sum dp_{U,lst,t}[\operatorname{popcount(t)}=k]\)。
但是时间复杂度有点不牛,考虑优化。注意到实际上这个 \(s\) 是没有任何作用的,因为我们任何时刻都只在意当前 \(1\sim i\) 这些值的相对位置。所以考虑定义状态函数 \(dp_{i,lst,t}\) 表示插入 \(1\sim i\) 这些值,最后一个属于题目给定的那 \(k\) 个数的值的相对位置是 \(lst\),\(g\) 数组的差分是 \(t\) 的方案数。
-
如果 \(i\) 不属于题目给定的 \(k\) 个数。枚举 \(t\) 和 \(i\) 插入的位置 \(pos\),表示 \(i\) 插在第 \(pos\) 个数之后。可以 \(O(n)\) 得到 \(t'\),时间复杂度 \(O(n^22^n)\)。再枚举 \(lst\):
- \(lst \le pos\)。\(dp_{i,lst,t'}\gets dp_{i-1,lst,t}\)。
- \(lst>pos\)。\(dp_{i,lst+1,t'}\gets dp_{i-1,lst,t}\)。
时间复杂度依旧 \(O(n^22^n)\)。
-
如果 \(i\) 属于那 \(k\) 个数。还是枚举 \(t,pos\) 得到 \(t'\)。有转移:\(dp_{i,pos+1,t'}\gets dp_{i-1,lst,t}[lst \le pos]\)。
总时间复杂度 \(O(n^32^n)\),空间复杂度 \(O(n2^n)\)。
其实复杂度不是 \(O(n^22^n)\) 的。记:
则:
那么 \(s_n =2^{n+1}(n+1)^2 - 4g_n - 2^{n+2}+2\)。
那么 \(g_{n} = 2^{n+1}(n+1)-2^{n+2}+2\)。所以:
也就是说,这种做法其实是 \(O(n^22^n)\) 的时间复杂度。
构造映射
最有用的一种方式。在求本质不同的 \(B\) 的数量且得到 \(B\) 的路径不唯一时可以尝试构造映射。一般的方式是贪心或者 DP 或者通过固定策略构造。
AT_agc013_d [AGC013D] Piling Up
考虑取一次对两种颜色的影响。记 \(c\) 为红色的数量,那么 \(n-c\) 为蓝色的数量。记 \(R\) 为红色,\(B\) 为蓝色。
- \(RR\)。\(c \to c-1\)。要求之前 \(c \ge 1\)。
- \(RB\)。\(c \to c\)。要求之前 \(c \ge 1\)。
- \(BR\)。\(c \to c\)。要求之前 \(c \le n-1\)。
- \(BB\)。\(c \to c + 1\)。要求之前 \(c \le n-1\)。
不妨令初始时 \(c =0\),那么可以按照上面的操作得到 \(h_i\) 表示执行完第 \(i\) 轮后 \(c\) 的值。那么对于一个最终合法的 \(c_0\),应该满足:
- 对于所有 \(R*\) 操作的 \(i\),有 \(h_i + c_0 \ge 0\)。这里本来应该是 \(\ge 1\),但是执行 \(c \to c-1\) 后就变成 \(0\) 了。
- 对于所有 \(B*\) 操作的 \(i\),有 \(h_i + c_0 \le n\)。同上。
分情况讨论:
- 存在 \(R*\) 操作且 \(h_i < 0\)。那么此时如果该方案合法一定存在一个 \(c_0\)(也是最小的)使得 \(\min(h_i) + c_0 =0\)。这里 \(\min(h_i)\) 表示的是 \(R*\) 操作的 \(h\) 最小值。
- 不存在 \(R*\) 操作且 \(h_i < 0\)。此时直接让 \(c_0=0\) 一定合法。
那么这样就可以对所有方案构造映射了,有 \(f(G) = c_0\),其中 \(c_0\) 是最小可行的值。考虑分两类计数。
-
对于情况 \(1\)。维护 \(f_{i,j,0/1}\) 表示当前执行完前 \(i\) 轮操作,\(c = j\),且是否存在 \(R*\) 操作且 \(h_i + c_0 = 0\)。那么:
\[f_{0,c_0,0} = 1[1 \le c_0 \le n]\\ \]-
\[f_{i, j - 1,0}\gets f_{i - 1,j,0}[j > 1] (R)\\ f_{i,j-1,1}\gets f_{i - 1,j,0} [j = 1] (R)\\ f_{i,j - 1,1}\gets f_{i - 1,j,1} [j \ge 1] (R)\\ f_{i, j,0/1}\gets f_{i-1,j,0/1} [j \le n-1](B)\\ \]
-
\[f_{i,j + 1,0/1}= f'_{i,j,0/1} \]
-
\[f_{i, j - 1,0}\gets f'_{i,j,0}[j > 1] (R)\\ f_{i,j-1,1}\gets f'_{i,j,0} [j = 1] (R)\\ f_{i,j - 1,1}\gets f'_{i,j,1} [j \ge 1] (R)\\ f_{i, j,0/1}\gets f'_{i,j,0/1} [j \le n](B)\\ \]
最后答案就是 \(\sum f_{m,j,1}\)。复杂度 \(O(nm)\)。
-
-
对于情况 \(2\)。定义同上。有:
\[f_{0,0} = 1 \]-
\[f_{i,j - 1}\gets f_{i-1,j} [j \ge 1](R)\\ f_{i,j}\gets f_{i-1,j} [j \le n-1](B) \]
-
\[f_{i,j+1}= f'_{i,j} \]
-
\[f_{i,j - 1}\gets f'_{i,j} [j \ge 1](R)\\ f_{i,j}\gets f'_{i,j} [j \le n](B) \]
答案为 \(\sum f_{m,j}\)。时间复杂度 \(O(nm)\)。
-
最后把两个加起来就行了。
其实可以合一起,第二个相当于初始化 \(f_{0,0,1}=1\)。
AT_agc002_f [AGC002F] Leftmost Ball
考虑判断一种方案是否合法。应该满足对于任意前缀 \(1\sim i\),都有 \(0\) 的数量不小于颜色数量,且最后每种颜色恰好出现了 \(k-1\) 次。
考虑定义两种方案本质相同,当仅当 \(0\) 的位置相同,且出现位置集合构成的集合相同。比如 \(0012\) 和 \(0021\) 是相同的。那么我们就只需要求有多少本质不同的方案,再乘上 \(n!\) 即可。
此时第 \(i\) 个 \(0\) 可以直接视为颜色 \(i\) 的开头。那么就可以用插入的方式生成序列了。具体的,定义 \(f_{i,j}\) 表示插入完颜色 \(1 \sim i\),且 \(i\) 这个颜色 \(0\) 的位置在 \(j\) 的方案数。则我们只需要保证插入 \(i + 1\) 这个颜色后颜色第一次出现的位置在 \(j\) 后面即可。有:
前缀和优化即可。最后答案就是 \(n!\sum f_{n,i}\)。但是时间复杂度是 \(O(n^2K)\) 的。
CF2048H Kevin and Strange Operation
显然最后每个位置将会是原来序列上一段区间的 \(\max\)。
记当前序列上第 \(i\) 个数是原序列 \([l_i,r_i]\) 的最大值。
考虑操作一个位置 \(p\)。有:
- 将 \(r_i\) 变成 \(r_{i+1}\),对于 \(1 \le i < p\)。
- 删掉 \([l_p,r_p]\)。
那么相当于是将 \([l_1,r_1],[l_2,r_2],\dots,[l_m,r_m]\) 变成 \([l_1,r_2],[l_2,r_3],\dots,[l_{p-1},r_p],[l_{p+1},r_{p+1}],\dots,[l_m,r_m]\)。
将 \(l,r\) 看作两个序列,那么操作 \(p\) 就会让 \(r_1\) 删掉,让 \(l_p\) 删掉。初始时 \(l_i = i,r_i = i\)。
因为 \(r\) 是删前缀的过程,\(l\) 是删子序列的过程。所以考虑判定一个序列是否合法。具体地,如果这个序列长度为 \(m\),那么可以知道 \(r_i = n - i +1\)。维护 \(l_i\) 表示从后往前尽可能少地移动 \(l\),匹配到第 \(i\) 个时 \(l\) 的值。那么 \(l_i\) 应该是 \([1, \min(l_{i+1},n-i+1)]\) 中最大的一个 \(j\),使得 \(\max\limits_{k=j}^{n-i+1}a_k = b_i\)。显然,不合法当且仅当某一个 \(i\) 时不存在这样的 \(j\) 了。
这启发我们去维护这个 \(l\)。定义状态函数 \(f_{i,j}\) 表示后面 \(i\) 个数都保留下来了,当前的 \(l\) 是 \(j\) 且合法的方案数。分类转移:
- \(b\) 序列中这一位是 \(0\)。找到 \(i\) 前面第一个 \(1\) 的位置 \(j\)。首先要满足 \(a_i =0\)。
- \(l_{i+1} > i\)。\(f_{i,i} \gets \sum\limits_{j > i}^{}f_{i+1,j}\)。
- \(j + 1 < l_{i+1} \le i\)。\(f_{i,k - 1}\gets f_{i+1,k}\)。
- \(l_{i+1} \le j+1\)。不合法。
- \(b\) 序列这一位是 \(1\)。找到 \(i\) 前面第一个 \(1\) 的位置 \(j\)。
- \(j < l_{i+1}\)。\(f_{i,j}\gets \sum\limits_{k>j}^{}f_{i+1,k}\)。
- \(l_{i+1} \le j\)。\(f_{i,k - 1}\gets f_{i+1,k}\)。
最后答案就是 \(\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{i}f_{i,j}\)。直接做时间复杂度 \(O(n^2)\)。
将这两个转移合在一起,有:
- \(f_{i,k} = [a_i = 0] f_{i+1,k+1}\),对于 \(k=i\)。
- \(f_{i,k} = [a_i=0]f_{i+1,k+1}\),对于 \(j < k \le i - 1\)。
- \(f_{i,k} = \sum\limits_{x = k + 1}^{i+1}f_{i+1,x}\),对于 \(k=j\)。
- \(f_{i,k} = f_{i,k+1}\),对于 \(k < j\)。
明显是一个整体 DP 的形式。首先这个平移是整体平移的,所以可以不管。维护 \(\delta_i = n-i\) 表示平移量。
首先 \(k -\delta_i > i\) 的 \(f\) 值和 \(k - \delta = 0\) 的 \(f\) 值需要清空。也就是 \(f_{k} = 0[k > i+\delta_i\lor k = \delta]\)。
其次如果 \(a_i \ne 0\)。\(f_{k} = 0[k > j +\delta_i ]\)。
构造最后对于 \(k = j + \delta_i\),有 \(f_{k} = \sum\limits_{x = k}^{n}f'_x\)。
线段树维护可以做到 \(O(n\log n)\)。
CF1383E Strange Operation
这不是我们 CF2048H 吗?
首先问题可以变成划分若干个区间,每个区间权值为区间 \(\max\),求不同权值序列数量。
考虑如果已知最后第 \(i\) 个区间的权值是 \(b_i\),能否构造映射来判定合法性。把连续的 \(0\) 段和起来,记 \(p_i\) 为匹配到第 \(i\) 个权值时在 \(s\) 中的位置,则:
- \(b_{i+1}=1\)。找到 \(p_i\) 后面第一个 \(1\) 的位置 \(k\),\(p_{i+1}=k\)。
- \(b_{i+1}=0\)。因为把连续 \(0\) 合并了,记 \(cnt\) 为 \(0\) 的数量。则找到后面第一个 \(k\),使得 \([k-cnt+1,k]\) 全是 \(0\),\(p_{i+1}=k\)。
对于最后一段连续的 \(0\) 单独处理,则对于 \(b_m=1\) 的合法判定当仅当中途不存在找不到 \(k\) 的情况。
那么考虑对这个 DP。定义状态函数 \(f_i\) 表示匹配到 \(i\) 的方案数,则:
- \(s_i = 1\)。能匹配到 \(i\) 当仅当 \(b_{j+1} = 1\) 且 \(i\) 是 \(p_j\) 后面第一个 \(1\),此时 \(f_i = \sum\limits_{j=pre_i}^{i-1} f_j\)。其中 \(pre_i\) 为 \(i\) 前面第一个 \(1\) 的位置。
- \(s_i=0\)。既然能匹配到 \(i\),则 \(0\) 的数量一定等于 \(i\) 前面连续 \(0\) 的数量,记这个为 \(cnt_i\)。\(i\) 前面第一个 \(cnt_j \ge cnt_i\) 的位置 \(j\),则 \(f_i=\sum\limits_{k=j + 1}^{i-1}f_k[s_k=1]\),特别的,当 \(i\) 前面全是 \(0\) 时 \(j = -1\)。
对于答案,首先判掉 \(b\) 全是 \(0\) 的情况。然后枚举最后一个 \(1\) 的匹配 \(i\),记 \(g_i\) 为 \([i,n]\) 连续 \(0\) 的后缀长度。则 \(ans =\sum\limits_{i=1}^{n}f_i \times (g_{i+1}+1)[s_i=1]\)。
时间复杂度 \(O(n)\)。

浙公网安备 33010602011771号