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\) 的方案数。考虑转移:

  1. 加入 N\(f_{i,c_{s,N},1}\gets f_{i-1,s,0/1/2}\)
  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}\)
  3. 加入 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}\)。那么:

\[dp_{i,j} = \max(dp_{i-1,j},dp_{i,j-1},dp_{i-1,j-1} +1 [s_i = t_j]) \]

如果我们把 \(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\)

  1. \(f_{i,x + c} = f_{i,x+c}| f_{i-1,x}\)
  2. \(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\) 就行了。顺着扫过去,维护一个栈:

  1. 加入一个 \(0\)
    1. 最后两个是 \(00\)。此时形成 \(000\),将其变成 \(0\) 不劣。
    2. 最后两个不是 \(00\)。先加进来,因为后面可能是 \(0\),此时可能变成情况 \(1.1\)
  2. 加入一个 \(1\)
    1. 最后一个是 \(0\)。因为删掉 \(01\)\(0,1\) 数量影响不变,且如果最后剩下一个 \(1\) 这一对 \(01\) 中的 \(0\) 必须带走一个 \(1\)\(1\) 必须带走一个 \(0\)。所以删掉 \(01\) 不劣。
    2. 最后一个是 \(1\)。加进来不管。

显然,任意时刻栈中元素都是形如 $111\dots 11 0 0 $,其中 \(0\) 的数量不超过 \(2\)。那么最后 \(S\) 合法,当且仅当最后复杂度 \(0\) 的数量小于 \(1\) 的数量。

那么尝试对这玩意 DP。定义状态函数 \(f_{i,x,y}\) 表示前 \(i\) 个字符,当前栈是 \(x\)\(1\) 后面加上 \(y\)\(0\) 的结构的方案数。那么:

  1. \(S_i = 0\)\(f_{i,x,1} \gets f_{i-1,x,0/2},f_{i,x,2}\gets f_{i-1,x,1}\)
  2. \(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\) 求合法子串数量。因为:

\[((\sum\limits_{i=1}^{x-1}i\bmod 11) + (y + 1) + 9)\bmod 11\\ =((\sum\limits_{i=1}^{x-1}i\bmod 11) +y-1)\bmod 11\\ =(((\lfloor\frac{x}{11}\rfloor+1)\times \frac{10\times 11}{2}-\sum\limits_{i=x\bmod 11}^{10}i)+y-1)\bmod 11\\ =((-\sum\limits_{i= x\bmod 11}^{10}i ) + y - 1)\bmod 11 \]

所以我们只需要维护当前子串排名对 \(11\) 取模的结果。定义 \(f_{i,j}\) 表示所有以 \(i\) 结尾的子串,排名对 \(11\) 取模为 \(j\) 的数量。考虑 $i -1\to i $ 的情况:

  1. 字符串是 \(s_{i}\)\(f_{i,s_i}\gets 1[s_i > 0]\)
  2. 字符串是 \(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_{u,1} = val_u + \sum\limits_{v\in son_u}^{} f_{v,0}\\ f_{u,0} = \sum\limits_{v\in son_u}^{} \max(f_{v,0},f_{v,1}) \]

一个暴力的思路是,直接去维护 \(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})\),也就是将题目限制变成:

\[f_{u,0} = \sum\limits_{v\in son_u}^{} f_{v,1}\\ f_{u,1} = \max(\sum\limits_{v\in son_u}^{} f_{v,1}, val_u + \sum\limits_{v\in son_u}^{} f_{v,0}) \]

\(\delta_u = f_{u,1} - f_{u,0}\)。那么转移方程又可以变成:

\[f_{u,0} = \sum\limits_{v\in son_u}^{} f_{v,1}\\ f_{u,1} =\max(\sum\limits_{v\in son_u}^{}f_{v,1}, val_u + \sum\limits_{v\in son_u}^{}f_{v,1} -\sum\limits_{v\in son_u}^{}\delta_v)\\ \delta_u = \max(0,val_u - \sum\limits_{v\in son_u}^{}\delta_v) \]

发现此时 \(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\) 的方案数,有:

\[g_{x + x_0,y+y_0}\gets g'_{x,y}\times dp_{v,x_0,y_0} \]

然后枚举 \(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}\)。那么有转移方程:

\[f_{i} = f_{i-1} [s_i = t_i]\\ f_{i} = f_{i-2} [c1(s_{i-1,i},t_{i-1,i}) ]\\ f_{i}= f_{i-4} [c2(s_{i-3,i-2,i-1,i},t_{i-3,i-2,i-1,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\) 的可行性。

  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\}]) \]

  2. \(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\),有:

  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\}] \]

  2. \(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\) 的方案数。

  1. 如果 \(i\) 不属于题目给定的 \(k\) 个数。枚举 \(t\)\(i\) 插入的位置 \(pos\),表示 \(i\) 插在第 \(pos\) 个数之后。可以 \(O(n)\) 得到 \(t'\),时间复杂度 \(O(n^22^n)\)。再枚举 \(lst\)

    1. \(lst \le pos\)\(dp_{i,lst,t'}\gets dp_{i-1,lst,t}\)
    2. \(lst>pos\)\(dp_{i,lst+1,t'}\gets dp_{i-1,lst,t}\)

    时间复杂度依旧 \(O(n^22^n)\)

  2. 如果 \(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 =\sum\limits_{i=1}^{n}2^ii^2,g_n = \sum\limits_{i=1}^{n}2^ii\\ \]

则:

\[s_{n+1} = \sum\limits_{i=1}^{n+1}2^ii^2\\ = 2^11^2 + \sum\limits_{i=1}^{n}2^{i+1}(i+1)^2\\ =2 + 2(\sum\limits_{i=1}^{n}2^i (i^2+2i+1))\\ =2s_n + 4\sum\limits_{i=1}^{n}2^ii + \sum\limits_{i=1}^{n + 1}2^i\\ =2s_n + 4g_n + 2^{n+2}-2\\ =s_n + 2^{n+1}(n+1)^2\\ \]

那么 \(s_n =2^{n+1}(n+1)^2 - 4g_n - 2^{n+2}+2\)

\[g_{n+1}=\sum\limits_{i=1}^{n+1}2^ii\\ =2^11+\sum\limits_{i=1}^{n}2^{i+1}(i+1)\\ =2+2g_n + \sum\limits_{i=2}^{n+1}2^i\\ =2g_n+2^{n+2}-2\\ =g_n + 2^{n+1}(n+1) \]

那么 \(g_{n} = 2^{n+1}(n+1)-2^{n+2}+2\)。所以:

\[s_n \\=2^{n+1}(n+1)^2 - 4(2^{n+1}(n+1)-2^{n+2}+2)-2^{n+2}+2\\=2^{n+1}(n+1)^2 -2^{n+3}(n+1)+2^{n+4}-2^{3}-2^{n+2}+2 \]

也就是说,这种做法其实是 \(O(n^22^n)\) 的时间复杂度。

构造映射

最有用的一种方式。在求本质不同的 \(B\) 的数量且得到 \(B\) 的路径不唯一时可以尝试构造映射。一般的方式是贪心或者 DP 或者通过固定策略构造。

AT_agc013_d [AGC013D] Piling Up

考虑取一次对两种颜色的影响。记 \(c\) 为红色的数量,那么 \(n-c\) 为蓝色的数量。记 \(R\) 为红色,\(B\) 为蓝色。

  1. \(RR\)\(c \to c-1\)。要求之前 \(c \ge 1\)
  2. \(RB\)\(c \to c\)。要求之前 \(c \ge 1\)
  3. \(BR\)\(c \to c\)。要求之前 \(c \le n-1\)
  4. \(BB\)\(c \to c + 1\)。要求之前 \(c \le n-1\)

不妨令初始时 \(c =0\),那么可以按照上面的操作得到 \(h_i\) 表示执行完第 \(i\) 轮后 \(c\) 的值。那么对于一个最终合法的 \(c_0\),应该满足:

  1. 对于所有 \(R*\) 操作的 \(i\),有 \(h_i + c_0 \ge 0\)。这里本来应该是 \(\ge 1\),但是执行 \(c \to c-1\) 后就变成 \(0\) 了。
  2. 对于所有 \(B*\) 操作的 \(i\),有 \(h_i + c_0 \le n\)。同上。

分情况讨论:

  1. 存在 \(R*\) 操作且 \(h_i < 0\)。那么此时如果该方案合法一定存在一个 \(c_0\)(也是最小的)使得 \(\min(h_i) + c_0 =0\)。这里 \(\min(h_i)\) 表示的是 \(R*\) 操作的 \(h\) 最小值。
  2. 不存在 \(R*\) 操作且 \(h_i < 0\)。此时直接让 \(c_0=0\) 一定合法。

那么这样就可以对所有方案构造映射了,有 \(f(G) = c_0\),其中 \(c_0\) 是最小可行的值。考虑分两类计数。

  1. 对于情况 \(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]\\ \]

    1. \[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)\\ \]

    2. \[f_{i,j + 1,0/1}= f'_{i,j,0/1} \]

    3. \[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. 对于情况 \(2\)。定义同上。有:

    \[f_{0,0} = 1 \]

    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) \]

    2. \[f_{i,j+1}= f'_{i,j} \]

    3. \[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\) 后面即可。有:

\[f_{i+1,k}\gets f_{i,j}\times \binom{i\times K -k + (K - 1)}{K - 1}[j < k] \]

前缀和优化即可。最后答案就是 \(n!\sum f_{n,i}\)。但是时间复杂度是 \(O(n^2K)\) 的。

CF2048H Kevin and Strange Operation

显然最后每个位置将会是原来序列上一段区间的 \(\max\)

记当前序列上第 \(i\) 个数是原序列 \([l_i,r_i]\) 的最大值。

考虑操作一个位置 \(p\)。有:

  1. \(r_i\) 变成 \(r_{i+1}\),对于 \(1 \le i < p\)
  2. 删掉 \([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\) 且合法的方案数。分类转移:

  1. \(b\) 序列中这一位是 \(0\)。找到 \(i\) 前面第一个 \(1\) 的位置 \(j\)。首先要满足 \(a_i =0\)
    1. \(l_{i+1} > i\)\(f_{i,i} \gets \sum\limits_{j > i}^{}f_{i+1,j}\)
    2. \(j + 1 < l_{i+1} \le i\)\(f_{i,k - 1}\gets f_{i+1,k}\)
    3. \(l_{i+1} \le j+1\)。不合法。
  2. \(b\) 序列这一位是 \(1\)。找到 \(i\) 前面第一个 \(1\) 的位置 \(j\)
    1. \(j < l_{i+1}\)\(f_{i,j}\gets \sum\limits_{k>j}^{}f_{i+1,k}\)
    2. \(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)\)

将这两个转移合在一起,有:

  1. \(f_{i,k} = [a_i = 0] f_{i+1,k+1}\),对于 \(k=i\)
  2. \(f_{i,k} = [a_i=0]f_{i+1,k+1}\),对于 \(j < k \le i - 1\)
  3. \(f_{i,k} = \sum\limits_{x = k + 1}^{i+1}f_{i+1,x}\),对于 \(k=j\)
  4. \(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\) 中的位置,则:

  1. \(b_{i+1}=1\)。找到 \(p_i\) 后面第一个 \(1\) 的位置 \(k\)\(p_{i+1}=k\)
  2. \(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\) 的方案数,则:

  1. \(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\) 的位置。
  2. \(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)\)

posted @ 2026-04-11 17:29  harmis_yz  阅读(73)  评论(1)    收藏  举报