我是靠谱客的博主 尊敬钢铁侠,这篇文章主要介绍【集训队作业】IOI 2020 集训队作业 试题泛做 4Atcoder Grand Contest 35Atcoder Grand Contest 36Atcoder Grand Contest 37Atcoder Grand Contest 38Atcoder Grand Contest 39,现在分享给大家,希望可以做个参考。

Atcoder Grand Contest 35

Problem C. Skolem XOR Tree

N N N 2 2 2 的次幂时无解,因为不可能有路径在那一位上异或和为 1 1 1 ,以下算法对于剩余情况均给出了一组构造。

对于一个异或和为 0 0 0 的点集,我们可以将其仿照样例的解排成一排,即可满足这些点的限制。

注意到对于 4 4 4 的倍数 N N N ,有 N ⊕ ( N + 1 ) ⊕ ( N + 2 ) ⊕ ( N + 3 ) = 0 Noplus(N+1)oplus(N+2)oplus(N+3)=0 N(N+1)(N+2)(N+3)=0

因此,记 1 , 2 , … , N 1,2,dots,N 1,2,,N 的异或和为 S ( N ) S(N) S(N) ,有
S ( N ) = { N N ≡ 0   ( m o d   4 ) 1 N ≡ 1   ( m o d   4 ) N + 1 N ≡ 2   ( m o d   4 ) 0 N ≡ 3   ( m o d   4 ) S(N)=left{begin{array}{rcl}N&&{Nequiv0 (mod 4)}\1&&{Nequiv1 (mod 4)}\N+1&&{Nequiv2 (mod 4)}\0&&{Nequiv3 (mod 4)}end{array} right. S(N)=N1N+10N0 (mod 4)N1 (mod 4)N2 (mod 4)N3 (mod 4)

因此,对于 N ≡ 3   ( m o d   4 ) Nequiv3 (mod 4) N3 (mod 4) 的情况,可以直接仿照样例。

对于 N ≡ 1   ( m o d   4 ) Nequiv1 (mod 4) N1 (mod 4) 的情况,可以直接仿照样例排列 2 , 3 , … , N 2,3,dots,N 2,3,,N ,再将 1 , N + 1 1,N+1 1,N+1 分别接在 2 , 3 2,3 2,3 两侧即可。

对于 N ≡ 0   ( m o d   4 ) Nequiv0 (mod 4) N0 (mod 4) 的情况,可以直接仿照样例排列 1 , 2 , … , N − 1 1,2,dots,N-1 1,2,,N1 ,我们需要使得异或和为 N N N 的两个数相邻,令 N N N 的最高位为 X X X ,可以交换 X − 1 , X ⊕ N X-1,Xoplus N X1,XN ,使得 X ⊕ N , X Xoplus N,X XN,X 排在相邻的位置。

对于 N ≡ 2   ( m o d   4 ) Nequiv2 (mod 4) N2 (mod 4) 的情况,可以直接仿照样例排列 2 , 3 , … , N − 1 2,3,dots,N-1 2,3,,N1 ,我们需要使得异或和为 1 , N 1,N 1,N 的两个数相邻,令 N N N 的最高位为 X X X ,可以交换 X − 1 , X ⊕ N X-1,Xoplus N X1,XN ,使得 X ⊕ N , X Xoplus N,X XN,X 排在相邻的位置,若交换涉及到 2 , 3 2,3 2,3 ,则把另一个也对应地交换过去即可。

时间复杂度 O ( N ) O(N) O(N)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int a[MAXN];
int main() {
	int n, power = 1; read(n);
	while (power < n) power <<= 1;
	if (n == power) {
		puts("No");
		return 0;
	}
	puts("Yes");
	power >>= 1;
	if (n % 4 == 0) {
		for (int i = 1; i <= n - 1; i++)
			a[i] = i;
		swap(a[power - 1], a[n ^ power]);
		for (int i = 1; i <= n - 2; i++)
			printf("%d %dn", a[i], a[i + 1]);
		printf("%d %dn", a[n - 1], n + a[1]);
		for (int i = 1; i <= n - 2; i++)
			printf("%d %dn", n + a[i], n + a[i + 1]);
		printf("%d %dn", n, power);
		printf("%d %dn", 2 * n, n ^ power);
	}
	if (n % 4 == 1) {
		for (int i = 2; i <= n - 1; i++)
			printf("%d %dn", i, i + 1);
		printf("%d %dn", n, n + 2);
		for (int i = 2; i <= n - 1; i++)
			printf("%d %dn", n + i, n + i + 1);
		printf("%d %dn", 1, 2);
		printf("%d %dn", n + 1, 3);
	}
	if (n % 4 == 2) {
		for (int i = 2; i <= n - 1; i++)
			a[i] = i;
		swap(a[power - 1], a[n ^ power]);
		if ((n ^ power) == 2 && n != 6) swap(a[power - 2], a[3]);
		for (int i = 2; i <= n - 2; i++)
			printf("%d %dn", a[i], a[i + 1]);
		printf("%d %dn", a[n - 1], n + a[2]);
		for (int i = 2; i <= n - 2; i++)
			printf("%d %dn", n + a[i], n + a[i + 1]);
		printf("%d %dn", n, power);
		printf("%d %dn", 2 * n, n ^ power);
		printf("%d %dn", 1, 2);
		printf("%d %dn", 1 + n, 3);
	}
	if (n % 4 == 3) {
		for (int i = 1; i <= 2 * n - 1; i++)
			printf("%d %dn", i, i + 1);
	}
	return 0;
}

Problem D. Add and Remove

考虑时间倒流,找到最后一次被操作的位置 x x x ,则 x x x 上当时的数字会被计算 2 2 2 倍的贡献,此后,问题可以递归至两侧解决。

我们需要在状态中计录 ( l , r , c l , c r ) (l,r,cl,cr) (l,r,cl,cr) ,表示考虑区间 [ l , r ] [l,r] [l,r] 内的操作, l − 1 l-1 l1 处的数字会被计算 c l cl cl 倍的贡献, r + 1 r+1 r+1 处的数字会被计算 c r cr cr 倍的贡献。

d p ( l , r , c l , c r ) dp(l,r,cl,cr) dp(l,r,cl,cr) 为该状态下的最优解,则有
d p ( l , r , c l , c r ) = min ⁡ i = l r { ( c l + c r ) a i + d p ( l , i − 1 , c l , c l + c r ) + d p ( i + 1 , r , c l + c r , c r ) } dp(l,r,cl,cr)=min_{i=l}^{r}{(cl+cr)a_i+dp(l,i-1,cl,cl+cr)+dp(i+1,r,cl+cr,cr)} dp(l,r,cl,cr)=i=lminr{(cl+cr)ai+dp(l,i1,cl,cl+cr)+dp(i+1,r,cl+cr,cr)}

考虑该 d p dp dp 的状态数, l , r l,r l,r 显然有 O ( N 2 ) O(N^2) O(N2) 组,可以发现 c l , c r cl,cr cl,cr 的取值与上述转移式中的 i i i 无关,因此每一层转移至多增加一倍,是 O ( 2 N ) O(2^N) O(2N) 级别的。

时间复杂度 O ( 2 N N 3 ) O(2^NN^3) O(2NN3) ,其中 N = 16 N=16 N=16 ,常数较小。

以下代码通过 s t d : : m a p std::map std::map 实现。

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 55;
const long long INF = 1e18;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, a[MAXN], Max;
map <pair <int, int>, ll> dp[MAXN][MAXN];
ll work(int l, int r, int cl, int cr) {
	if (l > r) return 0;
	if (dp[l][r].count(make_pair(cl, cr)))
		return dp[l][r][make_pair(cl, cr)];
	ll ans = INF; for (int i = l; i <= r; i++)
		chkmin(ans, 1ll * a[i] * (cl + cr) + work(l, i - 1, cl, cl + cr) + work(i + 1, r, cl + cr, cr));
	return dp[l][r][make_pair(cl, cr)] = ans;
}
int main() {
	read(n);
	for (int i = 1; i <= n; i++)
		read(a[i]);
	writeln(a[1] + a[n] + work(2, n - 1, 1, 1));
	return 0;
}

Problem E. Develop

考虑构建一张这样的图: i i i i − 2 , i + K i-2,i+K i2,i+K 连有向边。

考虑选定一个点集 S S S 删除,判断其是否可行。

若选定的点在上述的图中存在环,那么显然不可能全部删除,若不存在环,那么可以直接按照拓补序进行操作,全部删除。

因此,我们需要计算不构成环的点集 S S S 数。

K K K 为偶数,我们显然可以将奇数位和偶数位分开处理,要求即为不同时选定相邻的 K 2 + 1 frac{K}{2}+1 2K+1 个点,可以通过简单的 O ( N 2 ) O(N^2) O(N2) 动态规划完成。

K K K 为奇数,考虑一个由 + K , − 2 +K,-2 +K,2 组成的环,可以发现, + K +K +K 一定恰好出现两次,否则,就可以找到一个更小的环。

考虑使用类似的动态规划,在状态中计入 ( i , j , k , l j , l k ) (i,j,k,lj,lk) (i,j,k,lj,lk) ,分别表示处理了前 i i i 个位置,奇、偶数位当前分别连续选定了 j , k j,k j,k 个位置,奇数位不能连续选到 l j lj lj ,偶数位不能连续选到 l k lk lk

考虑奇数位 i i i 时的转移,若不选择 i i i 号位置,则有转移
( i − 1 , j , k , l j , l k ) ⇒ ( i , 0 , k , N + 1 , l k ) (i-1,j,k,lj,lk)Rightarrow(i,0,k,N+1,lk) (i1,j,k,lj,lk)(i,0,k,N+1,lk)

i ≠ l j ine lj i=lj ,则可以选择 i i i 号位置,同时,若 i − K ≥ i − 2 ∗ k + 1 i - K geq i - 2 * k + 1 iKi2k+1 ,也即偶数位的能够通过 + K +K +K 到达 i i i ,就需要更新 l k lk lk 的限制,有转移
( i − 1 , j , k , l j , l k ) ⇒ ( i , j + 1 , k , l j , max ⁡ { l k , i − 2 ∗ j + K } ) (i-1,j,k,lj,lk)Rightarrow(i,j+1,k,lj,max{lk,i - 2 * j + K}) (i1,j,k,lj,lk)(i,j+1,k,lj,max{lk,i2j+K})

否则,即不需要更新 l k lk lk 的限制,有转移
( i − 1 , j , k , l j , l k ) ⇒ ( i , j + 1 , k , l j , l k ) (i-1,j,k,lj,lk)Rightarrow(i,j+1,k,lj,lk) (i1,j,k,lj,lk)(i,j+1,k,lj,lk)

偶数位的转移类似。

注意到 l j , l k lj,lk lj,lk 中,只有对应的 j j j k k k 较大的一个才有意义,因此 ( l j , l k ) (lj,lk) (lj,lk) 的个数是 O ( K ) O(K) O(K) 级别的,总状态数就是 O ( N 3 K ) O(N^3K) O(N3K) 级别的了。

时间复杂度 O ( N 3 K ) O(N^3K) O(N3K) ,常数较小。

以下代码通过 s t d : : m a p std::map std::map 实现。

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 155;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, m, P;
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
map <pair <int, int>, int> dp[MAXN][MAXN][MAXN];
int main() {
	read(n), read(m), read(P);
	if (m % 2 == 0) {
		static int dp[MAXN][MAXN];
		m /= 2, dp[0][0] = 1;
		for (int i = 1; i <= n; i++)
		for (int j = 0; j <= m; j++) {
			update(dp[i][0], dp[i - 1][j]);
			if (j != m) update(dp[i][j + 1], dp[i - 1][j]);
		}
		int ans = 0, bns = 0;
		for (int i = 0; i <= m; i++) {
			update(ans, dp[n / 2][i]);
			update(bns, dp[(n + 1) / 2][i]);
		}
		writeln(1ll * ans * bns % P);
	} else {
		dp[0][0][0][make_pair(n + 1, n + 1)] = 1;
		for (int i = 1; i <= n; i++)
		for (int j = 0; j <= n; j++)
		for (int k = 0; k <= n; k++) {
			if (i & 1) {
				for (auto x : dp[i - 1][j][k]) {
					pair <int, int> tmp = x.first; tmp.first = n + 1;
					update(dp[i][0][k][tmp], x.second);
					if (i != x.first.first) {
						tmp = x.first;
						if (i - m >= i - 2 * k + 1) chkmin(tmp.second, i - 2 * j + m);
						if (tmp.second >= i) update(dp[i][j + 1][k][tmp], x.second);
					}
				}
			} else {
				for (auto x : dp[i - 1][j][k]) {
					pair <int, int> tmp = x.first; tmp.second = n + 1;
					update(dp[i][j][0][tmp], x.second);
					if (i != x.first.second) {
						tmp = x.first;
						if (i - m >= i - 2 * j + 1) chkmin(tmp.first, i - 2 * k + m);
						if (tmp.first >= i) update(dp[i][j][k + 1][tmp], x.second);
					}
				}
			}
		}
		int ans = 0;
		for (int i = 0; i <= n; i++)
		for (int j = 0; j <= n; j++) {
			for (auto x : dp[n][i][j])
				update(ans, x.second);
		}
		writeln(ans);
	}
	return 0;
}

Problem F. Two Histograms

对于一对数 ( i , j ) (i,j) (i,j) ,若 k i + 1 = j , l j = i k_i+1=j,l_j=i ki+1=j,lj=i ,我们可以令 k i k_i ki 1 1 1 l j l_j lj 1 1 1 ,得到的结果作用在网格上显然会得到相同的网格。

经过有限次上述操作,我们可以得到一个不能继续上述操作的数列,我们称这样的数列是标准的,以下证明标准的数列与可以到达的网格一一对应。

一个数列自然只会生成一个网格,因此我们只需要证明不同的标准数列生成的网格不同即可。

证明:
假设两个数列 ( l , k ) , ( l ′ , k ′ ) (l,k),(l',k') (l,k),(l,k) 不同,且生成了相同的网格。
j j j 为最小的满足 l j ≠ l j ′ l_jne l'_j lj=lj 的位置,且不失一般性地令 l j < l j ′ l_j<l'_j lj<lj
j = 1 j=1 j=1 ,考虑数列 ( l ′ , k ′ ) (l',k') (l,k) A l j ′ , j ≥ 1 A_{l'_j,j}geq1 Alj,j1 ,而 l j < l j ′ l_j<l'_j lj<lj ,因此 A l j ′ , j ≤ 1 A_{l'_j,j}leq1 Alj,j1 ,从而 A l j ′ , j = 1 , k l j ′ ′ = 0 A_{l'_j,j}=1,k'_{l'_j}=0 Alj,j=1,klj=0 ,可以发现, 数列 ( l ′ , k ′ ) (l',k') (l,k) 不是一个标准的数列,矛盾。
j > 1 j>1 j>1 ,同 j = 1 j=1 j=1 的情况,可以发现, A j , l j ′ = 1 A_{j,l'_j}=1 Aj,lj=1 ,因此 k l j ′ ≥ j , k l j ′ ′ < j k_{l'_j}geq j,k'_{l'_j}< j kljj,klj<j ,又因为数列 ( l ′ , k ′ ) (l',k') (l,k) 是一个标准的数列, k l j ′ ′ ≠ j − 1 k'_{l'_j}ne j-1 klj=j1 ,并且 l j − 1 = l j − 1 ′ l_{j-1}=l'_{j-1} lj1=lj1 ,因此数列 ( l , k ) (l,k) (l,k) 生成的网格在 A j − 1 , l j ′ A_{j-1,l'_j} Aj1,lj 处大于数列 ( l ′ , k ′ ) (l',k') (l,k) 生成的网格在 A j − 1 , l j ′ A_{j-1,l'_j} Aj1,lj 处的值,矛盾。

那么,我们只需要对标准的数列计数即可,由容斥原理,显然有
A n s = ∑ i = 0 min ⁡ { N , M } ( − 1 ) i × ( N i ) × ( M i ) × i ! × ( N + 1 ) M − i × ( M + 1 ) N − i Ans=sum_{i=0}^{min{N,M}}(-1)^itimesbinom{N}{i}timesbinom{M}{i}times i!times(N+1)^{M-i}times(M+1)^{N-i} Ans=i=0min{N,M}(1)i×(iN)×(iM)×i!×(N+1)Mi×(M+1)Ni

时间复杂度 O ( N ) O(N) O(N) O ( N L o g V ) O(NLogV) O(NLogV)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5e5 + 5;
const int P = 998244353;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int fac[MAXN], inv[MAXN];
int power(int x, int y) {
	if (y == 0) return 1;
	int tmp = power(x, y / 2);
	if (y % 2 == 0) return 1ll * tmp * tmp % P;
	else return 1ll * tmp * tmp % P * x % P;
}
int binom(int x, int y) {
	if (y > x) return 0;
	else return 1ll * fac[x] * inv[y] % P * inv[x - y] % P;
}
void init(int n) {
	fac[0] = 1;
	for (int i = 1; i <= n; i++)
		fac[i] = 1ll * fac[i - 1] * i % P;
	inv[n] = power(fac[n], P - 2);
	for (int i = n - 1; i >= 0; i--)
		inv[i] = inv[i + 1] * (i + 1ll) % P;
}
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
int main() {
	int n, m; read(n), read(m);
	init(max(n, m));
	int ans = 0;
	for (int i = 0; i <= min(n, m); i++)
		if (i & 1) update(ans, P - 1ll * binom(n, i) * binom(m, i) % P * fac[i] % P * power(n + 1, m - i) % P * power(m + 1, n - i) % P);
		else update(ans, 1ll * binom(n, i) * binom(m, i) % P * fac[i] % P * power(n + 1, m - i) % P * power(m + 1, n - i) % P);
	writeln(ans);
	return 0;
}

Atcoder Grand Contest 36

Problem D. Negative Cycle

考虑 0 0 0 号节点到每个节点 i i i 的最短路 d i s i dis_i disi ,由于本来存在的边, d i s i dis_i disi 是递减的,且相邻的位置相差至多为 1 1 1

考虑一个确定的最短路序列 d i s i dis_i disi ,我们需要删掉一些边以确保其不能被更新,所花费的代价即为 d i s i dis_i disi 对应的代价。

计算代价的过程是直观的,考虑相邻的两段 d i s i dis_i disi 相同的区间 [ a , b ] , [ b + 1 , c ] [a,b],[b+1,c] [a,b],[b+1,c] ,其中 [ b + 1 , c ] [b+1,c] [b+1,c] 中连出的边需要删掉连向 [ b + 2 , c ] [b+2,c] [b+2,c] 的向前的边,以及连向 [ 0 , a − 1 ] [0,a-1] [0,a1] 的向后的边。

不难发现在状态中计入 a , b a,b a,b ,转移时枚举 c c c ,利用部分和优化即可得到一个 O ( N 3 ) O(N^3) O(N3) 的动态规划解法。

时间复杂度 O ( N 3 ) O(N^3) O(N3)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 505;
const long long INF = 1e18;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, a[MAXN][MAXN];
ll s[MAXN][MAXN], cost[MAXN][MAXN], dp[MAXN][MAXN];
int main() {
	read(n);
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j < i; j++)
			read(a[i][j]);
		for (int j = i + 1; j <= n; j++)
			read(a[i][j]);
	}
	for (int i = 1; i <= n; i++)
	for (int j = 1; j <= n; j++)
		s[i][j] = s[i][j - 1] + a[i][j];
	for (int i = 0; i <= n; i++)
	for (int j = i + 1; j <= n; j++)
		dp[i][j] = INF;
	for (int i = 1; i <= n; i++)
	for (int j = i; j <= n; j++) {
		ll res = 0;
		for (int k = i; k <= j; k++)
			res += s[k][j] - s[k][k];
		cost[i][j] = res;
	}
	for (int i = 1; i <= n; i++)
		dp[0][i] = cost[1][i];
	for (int i = 0; i <= n - 1; i++)
	for (int j = i + 1; j <= n; j++) {
		ll tmp = dp[i][j];
		for (int k = j + 1; k <= n; k++) {
			tmp += s[k][i];
			chkmin(dp[j][k], tmp + cost[j + 1][k]);
		}
	}
	ll ans = INF;
	for (int i = 0; i <= n - 1; i++)
		chkmin(ans, dp[i][n]);
	writeln(ans);
	return 0;
}

Problem E. ABC String

不难发现应该将连续的字符当成一个看待,记此时 A A A 的个数为 a a a B B B 的个数为 b b b C C C 的个数为 c c c ,不失一般性地设 a ≤ b ≤ c aleq bleq c abc

考虑选定一个 A A A 的集合,删除其余的 A A A ,并将相同的字符重新并起来,由于删除一个 A A A 至多使得 b , c b,c b,c 减少 1 1 1 ,因此依然有 a ≤ b , a ≤ c aleq b,aleq c ab,ac

仍然设 a ≤ b ≤ c aleq bleq c abc ,考虑删除一些 B , C B,C B,C ,使得字符串满足条件。

b = c b=c b=c ,我们可以不断地删除不会使得字符串中出现 A A AA AA B C BC BC C B CB CB

否则,即 b < c b<c b<c ,记总共 a + 1 a+1 a+1 段字符中包含 B B B 的段数为 x x x ,中间的 a − 1 a-1 a1 段字符中仅由一个 C C C 组成的段数为 y y y

x < y x<y x<y ,显然是无法通过删除一些 B , C B,C B,C 使得 b = c b=c b=c 的,否则,就说明包含 B B B 的段数不少于仅由一个 C C C 组成的段数,可以不断地删除不会使得字符串中出现连续相同字符的 C C C ,使得 b = c b=c b=c

因此,我们需要在原串中选出尽量多的 A A A ,满足得到的字符串中 x ≥ y xgeq y xy

考虑先选取所有的 A A A ,若 x ≥ y xgeq y xy ,则找到的一定是最优解,否则记 d = y − x d=y-x d=yx ,删去 d d d 个与单独的 C C C 相邻的 A A A 即可。

时间复杂度 O ( ∣ S ∣ ) O(|S|) O(S)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
char s[MAXN]; int n;
vector <pair <int, int>> mod;
void modify(char *s, int n, int x, int y) {
	for (int i = 1; i <= n; i++)
		if (s[i] == x + 'A') s[i] = y + 'A';
		else if (s[i] == y + 'A') s[i] = x + 'A';
}
void init() {
	scanf("%s", s + 1);
	int m = strlen(s + 1);
	int cnt[3] = {0, 0, 0};
	for (int i = 1; i <= m; i++)
		if (s[n] != s[i]) {
			s[++n] = s[i];
			cnt[s[i] - 'A']++;
		}
	s[n + 1] = 0;
	if (cnt[0] > cnt[1]) swap(cnt[0], cnt[1]), modify(s, n, 0, 1), mod.emplace_back(0, 1);
	if (cnt[1] > cnt[2]) swap(cnt[1], cnt[2]), modify(s, n, 1, 2), mod.emplace_back(1, 2);
	if (cnt[0] > cnt[1]) swap(cnt[0], cnt[1]), modify(s, n, 0, 1), mod.emplace_back(0, 1);
	if (cnt[0] == 0) puts(""), exit(0);
}
void sela() {
	int b = 0, c = 0;
	static int pos[MAXN]; int cnt = 0;
	for (int i = 1; i <= n; i++)
		if (s[i] == 'A') pos[++cnt] = i;
	for (int i = 1; i <= cnt; i++) {
		bool occur = false;
		for (int j = pos[i - 1] + 1; j <= pos[i] - 1; j++)
			occur |= s[j] == 'B';
		b += occur;
	}
		bool occur = false;
		for (int j = pos[cnt] + 1; j <= n; j++)
			occur |= s[j] == 'B';
		b += occur;
	for (int i = 1; i <= cnt - 1; i++)
		c += pos[i] == pos[i + 1] - 2 && s[pos[i] + 1] == 'C';
	if (b >= c) return;
	int lft = c - b, tmp = n; n = 0;
	for (int i = 1; i <= tmp; i++)
		if (s[i] == 'A') {
			if (lft != 0 && n >= 2 && s[n] == 'C' && s[n - 1] == 'A') lft--;
			else s[++n] = s[i];
		} else if (s[n] != s[i]) s[++n] = s[i];
	assert(lft == 0);
}
void delc() {
	int delta = 0;
	for (int i = 1; i <= n; i++)
		if (s[i] == 'C') delta++;
		else if (s[i] == 'B') delta--;
	assert(delta >= 0);
	int tmp = n; n = 0; s[tmp + 1] = 0;
	for (int i = 1; i <= tmp; i++)
		if (s[i] == 'C') {
			if (delta != 0 && ((n >= 1 && s[n] != s[i + 1]) || n == 0)) delta--;
			else s[++n] = s[i];
		} else s[++n] = s[i];
	assert(delta == 0);
}
void delb() {
	int a = 0, b = 0, c = 0;
	for (int i = 1; i <= n; i++)
		if (s[i] == 'C') c++;
		else if (s[i] == 'B') b++;
		else a++;
	assert(b == c && b >= a);
	int tmp = n; n = 0; s[tmp + 1] = -1;
	for (int i = 1; i <= tmp; i++)
		if (s[i] == 'A') s[++n] = s[i];
		else if (b > a && n >= 1 && s[n] != 'A' && s[i] != s[n] && s[i + 1] != s[n - 1]) n--, b--, c--;
		else s[++n] = s[i];
	s[n + 1] = 0;
}
int main() {
	init();
	sela();
	delc();
	delb();
	while (!mod.empty()) {
		modify(s, n, mod.back().first, mod.back().second);
		mod.pop_back();
	}
	printf("%sn", s + 1);
	return 0;
}

Problem F. Square Constraints

考虑没有下界的情况,显然每一个点可以匹配的位置是一个前缀,记这些前缀的长度分别为 a 0 , a 1 , a 2 , …   ( a 0 ≤ a 1 ≤ a 2 ≤ …   ) a_0,a_1,a_2,dots (a_0leq a_1leq a_2leqdots) a0,a1,a2, (a0a1a2) ,那么方案数即为 ∏ i ( a i − i ) prod_{i}(a_i-i) i(aii)

f ( i ) f(i) f(i) 表示平方和 ≤ ( 2 N ) 2 leq (2N)^2 (2N)2 可以匹配的前缀长度, g ( i ) g(i) g(i) 表示平方和 < N 2 <N^2 <N2 可以匹配的前缀长度,若 i ≥ N igeq N iN 则为 0 0 0

考虑对下界进行容斥,一个直观地想法是选择 i i i 个位置取 g ( i ) g(i) g(i) ,其余位置取 f ( i ) f(i) f(i) ,排序后计算匹配数,乘上 ( − 1 ) i (-1)^i (1)i 计入答案。

一点关键的观察是如果将不是 0 0 0 f ( i ) , g ( i ) f(i),g(i) f(i),g(i) ,共 3 N 3N 3N 个数排序,所有 ≤ N − 1 leq N-1 N1 i i i 对应的 f ( i ) f(i) f(i) 会排在最后 N N N 个位置,这是因为这些数 ≥ N geq N N ,而其余数 ≤ N leq N N

据此,枚举上面选取的位置数 i i i ,我们可以直观地用一个 O ( N 2 ) O(N^2) O(N2) 的动态规划加速上面的枚举,即记 d p j , k dp_{j,k} dpj,k 表示考虑了排在前面的 j j j 个位置,选择了 k k k g ( i ) g(i) g(i) 的方案数,转移时将对应的 f ( i ) , g ( i ) f(i),g(i) f(i),g(i) 一同考虑即可。

时间复杂度 O ( N 3 ) O(N^3) O(N3)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 255;
const int MAXM = 505;
const int INF  = 1e9;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, m, Q, P;
int dp[MAXM][MAXN];
pair <int, int> a[MAXM];
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
int main() {
	read(n), read(Q), P = INF / Q * Q;
	int p = 2 * n - 1, q = 2 * n - 1;
	for (int i = 0; i <= 2 * n - 1; i++) {
		while (i * i + p * p > (2 * n) * (2 * n) && p >= 0) p--;
		while (i * i + q * q >= n * n && q >= 0) q--;
		if (q == -1) a[++m] = make_pair(p + 1, 0);
		else a[++m] = make_pair(q + 1, p + 1);
	}
	sort(a + 1, a + m + 1);
	int ans = 0;
	for (int k = 0; k <= n; k++) {
		memset(dp, 0, sizeof(dp)), dp[0][0] = 1;
		for (int i = 1, f = 0, g = 0; i <= m; f += a[i].second != 0, g += a[i].second == 0, i++)
		for (int j = 0; j <= k; j++) {
			int tmp = dp[i - 1][j];
			if (a[i].second == 0) update(dp[i][j], 1ll * tmp * (a[i].first - j - g) % P);
			else {
				if (j != k) update(dp[i][j + 1], 1ll * tmp * (a[i].first - j - g) % P);
				update(dp[i][j], 1ll * tmp * (a[i].second - k - n - (f - j)) % P);
			}
		}
		int now = dp[m][k];
		if (k & 1) update(ans, P - now);
		else update(ans, now);
	}
	writeln(ans % Q);
	return 0;
}

Atcoder Grand Contest 37

Problem D. Sorting a Grid

输入的每一行的顺序是无关的,因此,可以将输入描述为 N × M Ntimes M N×M 个二元组 ( x i , y i ) (x_i,y_i) (xi,yi) ,即 i i i S , T S,T S,T 中出现的行号 x i , y i x_i,y_i xi,yi

我们需要将 i i i 分配在 B B B 的第 x i x_i xi 行, C C C 的第 y i y_i yi 行,并且使它们在同一列。

考虑构建如下二分图,对于每一个 i i i ,连接左侧第 x i x_i xi 个点和右侧第 y i y_i yi 个点,则左侧与右侧每一个点的度数均为 M M M ,由 H a l l Hall Hall 定理,该图存在完美匹配。

因此,找到一个匹配用于填第一列,删掉对应的边,重复该过程即可。

时间复杂度 O ( N 3 N ) O(N^3sqrt{N}) O(N3N ) ,其中 N , M N,M N,M 同阶。

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 105;
const int MAXM = 1e4 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, m, s, res[MAXN];
bool mat[MAXN][MAXN];
namespace NetworkFlow {
	const int INF = 2e9;
	const int MAXP = 205;
	struct edge {
		int dest, flow;
		unsigned pos;
	};
	vector <edge> a[MAXP];
	int tot, s, t, dist[MAXP];
	unsigned curr[MAXP];
	void addedge(int x, int y, int z) {
		a[x].push_back((edge) {y, z, a[y].size()});
		a[y].push_back((edge) {x, 0, a[x].size() - 1});
	}
	void init() {
		for (int i = 1; i <= n; i++) {
			a[i].clear();
			a[i + n].clear();
		}
		a[s = 2 * n + 1].clear();
		a[t = 2 * n + 2].clear();
		for (int i = 1; i <= n; i++) {
			addedge(s, i, 1);
			addedge(i + n, t, 1);
		} 
		for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)
			if (mat[i][j]) addedge(i, j + n, 1);
	}
	int dinic(int pos, int limit) {
		if (pos == t) return limit;
		int used = 0, tmp;
		for (unsigned &i = curr[pos]; i < a[pos].size(); i++)
			if (a[pos][i].flow != 0 && dist[pos] + 1 == dist[a[pos][i].dest] && (tmp = dinic(a[pos][i].dest, min(limit - used, a[pos][i].flow)))) {
				used += tmp;
				a[pos][i].flow -= tmp;
				a[a[pos][i].dest][a[pos][i].pos].flow += tmp;
				if (used == limit) return used;
			}
		return used;
	}
	bool bfs() {
		static int q[MAXP];
		int l = 0, r = 0;
		memset(dist, 0, sizeof(dist));
		dist[s] = 1, q[0] = s;
		while (l <= r) {
			int tmp = q[l];
			for (unsigned i = 0; i < a[tmp].size(); i++)
				if (dist[a[tmp][i].dest] == 0 && a[tmp][i].flow != 0) {
					q[++r] = a[tmp][i].dest;
					dist[q[r]] = dist[tmp] + 1;
				}
			l++;
		}
		return dist[t] != 0;
	}
	void flow() {
		int ans = 0;
		while (bfs()) {
			memset(curr, 0, sizeof(curr));
			ans += dinic(s, INF);
		}
		assert(ans == n);
		for (int i = 1; i <= n; i++) {
			for (auto x : a[i]) {
				if (x.dest != s && x.flow == 0) res[i] = x.dest - n;
			}
		}
	}
}
int a[MAXN][MAXN], b[MAXN][MAXN];
int f[MAXN][MAXN], g[MAXN][MAXN];
pair <int, int> home[MAXM];
vector <int> e[MAXN][MAXN]; 
int main() {
	read(n), read(m);
	for (int i = 1; i <= n; i++)
	for (int j = 1; j <= m; j++) {
		read(a[i][j]);
		home[a[i][j]].first = i;
		b[i][j] = ++s;
		home[b[i][j]].second = i;
	}
	for (int i = 1; i <= n * m; i++)
		e[home[i].first][home[i].second].push_back(i);
	for (int k = 1; k <= m; k++) {
		for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)
			mat[i][j] = e[i][j].size() != 0;
		NetworkFlow :: init();
		NetworkFlow :: flow();
		for (int i = 1; i <= n; i++) {
			int tmp = e[i][res[i]].back();
			e[i][res[i]].pop_back();
			f[i][k] = tmp;
			g[res[i]][k] = tmp;
		}
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++)
			printf("%d ", f[i][j]);
		printf("n");
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++)
			printf("%d ", g[i][j]);
		printf("n");
	}
	return 0;
}

Problem E. Reversing and Concatenating

令字符串中出现的最小的字符为 M i n Min Min ,考虑答案串开头 M i n Min Min 的个数 L e n Len Len

若字符串最后一个字符为 M i n Min Min ,连续长度为 L L L ,则 L e n Len Len 应当与 L × 2 K Ltimes2^{K} L×2K 取最大值。

令字符串中间最长的一段字符 M i n Min Min 长度为 L L L ,则 L e n Len Len 应当与 L × 2 K − 1 Ltimes2^{K-1} L×2K1 取最大值。

使得 L e n Len Len 取到最大值的方式只有每一次操作使得末尾处的 M i n Min Min 的长度翻倍。

在所有可能使得 L e n Len Len 取到最大值的方式中取答案字典序最小的一种即可。

时间复杂度 O ( N 2 ) O(N^2) O(N2)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e4 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, k;
char Min, Max, ans[MAXN], s[MAXN];
void check(char *s, int k) {
	static char t[MAXN];
	int now = n, len = 0;
	while (now >= 1 && s[now] == Min) now--, len++;
	assert(len != 0);
	while (k != 0 && len < n) len *= 2, k--;
	if (len >= n) {
		for (int i = 1; i <= n; i++)
			ans[i] = Min;
		printf("%sn", ans + 1);
		exit(0);
	}
	for (int i = 1; i <= len; i++)
		t[i] = Min;
	for (int i = len + 1; i <= n; i++)
		t[i] = s[now--];
	for (int i = 1; i <= n; i++)
		if (t[i] < ans[i]) {
			for (int j = 1; j <= n; j++)
				ans[j] = t[j];
			return;
		} else if (t[i] > ans[i]) return;
}
int main() {
	read(n), read(k);
	scanf("%s", s + 1);
	Min = 'z', Max = 'a';
	for (int i = 1; i <= n; i++) {
		chkmin(Min, s[i]);
		chkmax(Max, s[i]);
		s[2 * n - i + 1] = s[i];
		ans[i] = 'z';
	}
	if (Min == Max) {
		printf("%sn", s + 1);
		return 0;
	}
	if (s[n] == Min) check(s, k);
	for (int i = n; i <= 2 * n; i++)
		if (s[i] == Min) check(s + i - n, k - 1);
	printf("%sn", ans + 1);
	return 0;
}

Problem F. Counting of Subarrays

考虑如何判定一个序列合法。

首先,若序列长度为 1 1 1 ,序列合法。

若序列仅包含一种元素,当且仅当序列长度 ≥ L geq L L ,序列合法。

否则,取一段极长的仅包含最小值 M i n Min Min 的区间 [ l i , r i ] [l_i,r_i] [li,ri] ,则要求 r i − l i + 1 ≥ L r_i-l_i+1geq L rili+1L ,并且以 ⌊ r i − l i + 1 L ⌋ lfloorfrac{r_i-l_i+1}{L}rfloor Lrili+1 M i n + 1 Min+1 Min+1 代替之得到的序列合法。

将问题拓展一下,为每一个位置指定两个数 X i , Y i X_i,Y_i Xi,Yi ,对于所有合法区间 [ l , r ] [l,r] [l,r] ,求 X l × Y r X_ltimes Y_r Xl×Yr 的和。

考虑上面的判断合法的过程,实际上我们可以将每一个区间放在一起考虑

即每次处理序列中现存的最小值 M i n Min Min 所在的位置,并计算新产生的 M i n + 1 Min+1 Min+1 对应的 X i , Y i X_i,Y_i Xi,Yi

考虑一段最小值 M i n Min Min 产生的 M i n + 1 Min+1 Min+1 对应的 X i , Y i X_i,Y_i Xi,Yi ,第 1 , 2 , … , L − 1 1,2,dots,L-1 1,2,,L1 个元素显然不能作为区间的右端点,不对 Y i Y_i Yi 产生贡献; L , L + 1 , … , 2 L − 1 L,L+1,dots,2L-1 L,L+1,,2L1 个元素作为区间的右端点可以产生一个 M i n + 1 Min+1 Min+1 ,对 Y 1 Y_1 Y1 产生贡献; 2 L , 2 L + 1 , … , 3 L − 1 2L,2L+1,dots,3L-1 2L,2L+1,,3L1 个元素作为区间的右端点可以产生两个 M i n + 1 Min+1 Min+1 ,对 Y 2 Y_2 Y2 产生贡献,以此类推, X i X_i Xi 同理。

具体细节建议参考代码,时间复杂度 O ( N L o g N ) O(NLogN) O(NLogN)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, l, x[MAXN];
pair <int, int> y[MAXN];
ll calc(vector <pair <int, int>> &a) {
	ll ans = 0, sum = 0;
	for (unsigned i = l - 1, j = 0; i < a.size(); i++, j++) {
		sum += a[j].first;
		ans += a[i].second * sum;
	}
	return ans;
}
int main() {
	read(n), read(l);
	for (int i = 1; i <= n; i++) {
		read(x[i]);
		y[i] = make_pair(x[i], i);
	}
	sort(y + 1, y + n + 1); ll ans = n; int val = 0, pos = 1;
	vector <pair <pair <int, int>, pair <int, int>>> a, b;
	while (true) {
		if (a.empty()) {
			if (pos > n) break;
			else val = y[pos].first;
		} else val++;
		while (val == y[pos].first) a.emplace_back(make_pair(y[pos].second, y[pos].second), make_pair(1, 1)), pos++;
		b.clear(), sort(a.begin(), a.end());
		for (unsigned i = 0, j; i < a.size(); i = j + 1) {
			j = i; while (j + 1 < a.size() && a[j + 1].first.first == a[j].first.second + 1) j++;
			int len = j - i + 1, cnt = len / l;
			if (cnt != 0) {
				vector <pair <int, int>> cur, where, value;
				for (unsigned k = i; k <= j; k++)
					cur.push_back(a[k].second);
				ans += calc(cur);
				for (int k = 1; k <= cnt; k++) {
					where.emplace_back(a[i].first.first + k - 1, (k == cnt) ? a[j].first.second : a[i].first.first + k - 1);
					value.emplace_back(0, 0);
				}
				for (unsigned k = i; k <= j; k++) {
					int tl = k - i + 1, tr = j - k + 1;
					if (tl >= l) value[tl / l - 1].second += a[k].second.second;
					if (tr >= l) value[cnt - tr / l].first += a[k].second.first;
				}
				ans -= calc(value);
				for (unsigned k = 0; k < where.size(); k++)
					b.emplace_back(where[k], value[k]);
			} 
		}
		a = b;
	}
	writeln(ans);
	return 0;
}

Atcoder Grand Contest 38

Problem E. Gachapon

考虑计算到达各个尚未完成的状态的概率之和,由期望的线性性,这也是答案。

令所有状态的指数型生成函数为 G ( x ) G(x) G(x) ,普通型生成函数为 g ( x ) g(x) g(x) ,已经完成的状态的指数型生成函数为 F ( x ) F(x) F(x) ,普通型生成函数为 f ( x ) f(x) f(x) ,那么我们要求的就是 g ( 1 ) − f ( 1 ) g(1)-f(1) g(1)f(1)

考虑计算 H ( x ) = G ( x ) − F ( x ) H(x)=G(x)-F(x) H(x)=G(x)F(x) ,令 p i = A i ∑ A i p_i=frac{A_i}{sum A_i} pi=AiAi ,有
H ( x ) = e x − ∏ i = 1 N ( e p i x − 1 − p i x − p i 2 x 2 2 − ⋯ − p i B i − 1 x B i − 1 ( B i − 1 ) ! ) H(x)=e^x-prod_{i=1}^{N}(e^{p_ix}-1-p_ix-frac{p_i^2x^2}{2}-dots-frac{p_i^{B_i-1}x^{B_i-1}}{(B_i-1)!}) H(x)=exi=1N(epix1pix2pi2x2(Bi1)!piBi1xBi1)

S A = ∑ A i , y = e 1 S A x SA=sum A_i,y=e^{frac{1}{SA}x} SA=Ai,y=eSA1x ,那么
H ( x ) = y S A − ∏ i = 1 N ( y A i − 1 − p i x − p i 2 x 2 2 − ⋯ − p i B i − 1 x B i − 1 ( B i − 1 ) ! ) H(x)=y^{SA}-prod_{i=1}^{N}(y^{A_i}-1-p_ix-frac{p_i^2x^2}{2}-dots-frac{p_i^{B_i-1}x^{B_i-1}}{(B_i-1)!}) H(x)=ySAi=1N(yAi1pix2pi2x2(Bi1)!piBi1xBi1)

暴力展开 H ( x ) H(x) H(x) 的计算式,对于各个 y i y^i yi ,展开关于 x x x 的多项式的时间复杂度均为 O ( ( ∑ B i ) 2 ) O((sum B_i)^2) O((Bi)2) ,因此展开的时间复杂度为 O ( ( ∑ A i ) × ( ∑ B i ) 2 ) O((sum A_i)times(sum B_i)^2) O((Ai)×(Bi)2)

接下来考虑如何将 H ( x ) H(x) H(x) 转为普通型生成函数求出 h ( 1 ) h(1) h(1)

对于形如 c y i cy^i cyi 的项,其对答案的贡献显然为
c 1 − i S A frac{c}{1-frac{i}{SA}} 1SAic

考虑形如 c y i x j cy^ix^j cyixj 的项,其对答案的贡献应当为
c ∑ k = 0 ∞ ( i S A ) k × ( k + 1 ) × ( k + 2 ) × ⋯ × ( k + j ) csum_{k=0}^{infty}(frac{i}{SA})^ktimes(k+1)times(k+2)timesdotstimes(k+j) ck=0(SAi)k×(k+1)×(k+2)××(k+j)

考虑对于 0 < p < 1 , i ∈ N 0<p<1,iinmathbb{N} 0<p<1,iN ,计算
S = ∑ k = 0 ∞ p k × k i S=sum_{k=0}^{infty}p^ktimes k^i S=k=0pk×ki

等式两侧同时乘以 p p p ,则有
p S = ∑ k = 0 ∞ p k + 1 × k i pS=sum_{k=0}^{infty}p^{k+1}times k^i pS=k=0pk+1×ki

那么
( 1 − p ) S = [ i = 0 ] + ∑ k = 0 ∞ p k + 1 × ( ( k + 1 ) i − k i ) (1-p)S=[i=0]+sum_{k=0}^{infty}p^{k+1}times ((k+1)^i-k^i) (1p)S=[i=0]+k=0pk+1×((k+1)iki)

( k + 1 ) i (k+1)^i (k+1)i 用二项式定理展开,即可将问题递归至更小的规模。

本部分时间复杂度也为 O ( ( ∑ A i ) × ( ∑ B i ) 2 ) O((sum A_i)times(sum B_i)^2) O((Ai)×(Bi)2)

总时间复杂度 O ( ( ∑ A i ) × ( ∑ B i ) 2 ) O((sum A_i)times(sum B_i)^2) O((Ai)×(Bi)2)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 405;
const int P = 998244353;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, sa, sb, a[MAXN], b[MAXN];
int f[MAXN][MAXN], g[MAXN], coef[MAXN][MAXN];
int fac[MAXN], inv[MAXN], binom[MAXN][MAXN];
int power(int x, int y) {
	if (y == 0) return 1;
	int tmp = power(x, y / 2);
	if (y % 2 == 0) return 1ll * tmp * tmp % P;
	else return 1ll * tmp * tmp % P * x % P;
}
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
int main() {
	read(n), sa = 0, f[0][0] = 1;
	for (int i = 1; i <= n; i++) {
		read(a[i]), read(b[i]);
		sa += a[i], sb += b[i];
	}
	fac[0] = inv[0] = binom[0][0] = 1;
	for (int i = 1; i <= max(sa, sb); i++) {
		fac[i] = 1ll * fac[i - 1] * i % P;
		inv[i] = power(fac[i], P - 2);
		binom[i][0] = 1;
		for (int j = 1; j <= i; j++) {
			binom[i][j] = (binom[i - 1][j - 1] + binom[i - 1][j]) % P;
		}
	}
	for (int x = 1, s = 0; x <= n; s += b[x++]) {
		memset(g, 0, sizeof(g));
		int p = 1ll * a[x] * power(sa, P - 2) % P;
		for (int i = 0, now = 1; i <= b[x] - 1; i++, now = 1ll * now * p % P)
			g[i] = (P - 1ll * now * inv[i] % P) % P;
		for (int i = sa; i >= 0; i--) {
			static int tmp[MAXN];
			memset(tmp, 0, sizeof(tmp));
			for (int j = 0; j <= b[x]; j++)
			for (int k = 0; k <= s; k++) {
				update(tmp[j + k], 1ll * f[i][k] * g[j] % P);
			}
			memcpy(f[i], tmp, sizeof(tmp));
			if (i >= a[x]) {
				for (int j = 0; j <= s; j++)
					update(f[i][j], f[i - a[x]][j]);
			}
		}
	}
	assert(f[sa][0] == 1);
	int ans = 0;
	for (int i = 0; i <= sb; i++)
		update(ans, 1ll * f[0][i] * fac[i] % P);
	coef[0][0] = 1;
	for (int i = 1; i <= sb; i++)
	for (int j = i; j >= 0; j--) {
		coef[i][j] = 1ll * coef[i - 1][j] * i % P;
		if (j != 0) update(coef[i][j], coef[i - 1][j - 1]);
	}
	for (int i = 1; i <= sa - 1; i++) {
		static int res[MAXN];
		int p = 1ll * i * power(sa, P - 2) % P;
		memset(res, 0, sizeof(res)), res[0] = power((1 - p + P) % P, P - 2);
		for (int j = 1; j <= sb; j++) {
			int tmp = 0;
			for (int k = 0; k <= j - 1; k++)
				update(tmp, 1ll * binom[j][k] * res[k] % P);
			res[j] = 1ll * tmp * p % P * res[0] % P;
		}
		for (int j = 0; j <= sb; j++) {
			int tmp = 0;
			for (int k = 0; k <= j; k++)
				update(tmp, 1ll * res[k] * coef[j][k] % P);
			update(ans, 1ll * tmp * f[i][j] % P);
		}
	}
	writeln((P - ans) % P);
	return 0;
}

Problem F. Two Permutations

将输入的排列 P , Q P,Q P,Q 看做两个置换,则其各个环或是整体被改为连向自身,或是不作修改。

注意到时间限制对于 1 0 5 10^5 105 的数据规模来说异常地大,考虑用最小割解题。

对于一个位置 i i i ,有如下几种情况:
( 1 ) (1) (1) P i = Q i = i P_i=Q_i=i Pi=Qi=i :此时无论是否改变都不会对答案造成影响。
( 2 ) (2) (2) P i = Q i ≠ i P_i=Q_ine i Pi=Qi=i :此时只改变 P , Q P,Q P,Q 中的一个可以使答案 + 1 +1 +1
( 3 ) (3) (3) P i = i ≠ Q i P_i=ine Q_i Pi=i=Qi :此时改变 Q Q Q 会使得答案 − 1 -1 1
( 4 ) (4) (4) Q i = i ≠ P i Q_i=ine P_i Qi=i=Pi :此时改变 P P P 会使得答案 − 1 -1 1
( 5 ) (5) (5) , , P i , Q i , i ,,P_i,Q_i,i PiQii 互不相同:同时改变 P , Q P,Q P,Q 会使得答案 − 1 -1 1

由于要使用最小割解题,我们不希望出现增益的条件,考虑对 ( 2 ) (2) (2) 稍作修改。
( 2 ) (2) (2) P i = Q i ≠ i P_i=Q_ine i Pi=Qi=i :此时不改变 P , Q P,Q P,Q ,或同时改变 P , Q P,Q P,Q 会使得答案 − 1 -1 1

对于 P P P 中的点,令将其划分在 S S S 集表示改变,划分在 T T T 集表示不改变;对于 Q Q Q 中的点,令将其划分在 T T T 集表示改变,划分在 S S S 集表示不改变。

那么对于上面的情况,我们分别可以按照如下方式连边。
( 2 ) (2) (2) P ↔ Q Pleftrightarrow Q PQ
( 3 ) (3) (3) S → Q Srightarrow Q SQ
( 4 ) (4) (4) P → T Prightarrow T PT
( 5 ) (5) (5) P → Q Prightarrow Q PQ

时间复杂度 O ( D i n i c ( N , N ) ) O(Dinic(N,N)) O(Dinic(N,N))

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const int INF = 2e9;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
namespace NetworkFlow {
	const int INF = 2e9;
	const int MAXP = 5e5 + 5;
	struct edge {
		int dest, flow;
		unsigned pos;
	};
	vector <edge> a[MAXP];
	int tot, s, t, dist[MAXP];
	unsigned curr[MAXP];
	void addedge(int x, int y, int z) {
		a[x].push_back((edge) {y, z, a[y].size()});
		a[y].push_back((edge) {x, 0, a[x].size() - 1});
	}
	int dinic(int pos, int limit) {
		if (pos == t) return limit;
		int used = 0, tmp;
		for (unsigned &i = curr[pos]; i < a[pos].size(); i++)
			if (a[pos][i].flow != 0 && dist[pos] + 1 == dist[a[pos][i].dest] && (tmp = dinic(a[pos][i].dest, min(limit - used, a[pos][i].flow)))) {
				used += tmp;
				a[pos][i].flow -= tmp;
				a[a[pos][i].dest][a[pos][i].pos].flow += tmp;
				if (used == limit) return used;
			}
		return used;
	}
	bool bfs() {
		static int q[MAXP];
		int l = 0, r = 0;
		memset(dist, 0, sizeof(dist));
		dist[s] = 1, q[0] = s;
		while (l <= r) {
			int tmp = q[l];
			for (unsigned i = 0; i < a[tmp].size(); i++)
				if (dist[a[tmp][i].dest] == 0 && a[tmp][i].flow != 0) {
					q[++r] = a[tmp][i].dest;
					dist[q[r]] = dist[tmp] + 1;
				}
			l++;
		}
		return dist[t] != 0;
	}
	int flow() {
		int ans = 0;
		while (bfs()) {
			memset(curr, 0, sizeof(curr));
			ans += dinic(s, INF);
		}
		return ans;
	}
}
int n, a[MAXN], b[MAXN];
int va[MAXN], vb[MAXN];
int main() {
	int n; read(n);
	for (int i = 1; i <= n; i++)
		read(a[i]), a[i]++;
	for (int i = 1; i <= n; i++)
		read(b[i]), b[i]++;
	for (int i = 1; i <= n; i++) {
		if (va[i]) continue;
		int pos = i;
		while (va[pos] == 0) {
			va[pos] = i;
			pos = a[pos];
		}
	}
	for (int i = 1; i <= n; i++) {
		if (vb[i]) continue;
		int pos = i;
		while (vb[pos] == 0) {
			vb[pos] = i + n;
			pos = b[pos];
		}
	}
	int ans = 0;
	for (int i = 1; i <= n; i++)
		ans += a[i] != b[i];
	int s = NetworkFlow :: s = 2 * n + 1;
	int t = NetworkFlow :: t = 2 * n + 2;
	for (int i = 1; i <= n; i++) {
		if (a[i] == b[i]) {
			if (a[i] == i) continue; ans += 1;
			NetworkFlow :: addedge(vb[i], va[i], 1);
			NetworkFlow :: addedge(va[i], vb[i], 1);
		} else {
			if (a[i] == i) NetworkFlow :: addedge(s, vb[i], 1);
			else if (b[i] == i) NetworkFlow :: addedge(va[i], t, 1);
			else NetworkFlow :: addedge(va[i], vb[i], 1);
		}
	}
	ans -= NetworkFlow :: flow();
	writeln(ans);
	return 0;
}

Atcoder Grand Contest 39

Problem D. Incenters

"I prefer solving problems about world history rather than MO geometry."

对于选定的三个点 A , B , C A,B,C A,B,C ,令 A ′ A' A 为不经过 A A A 的圆弧 B C BC BC 的中点, B ′ , C ′ B',C' B,C 同理。

可以证明, △ A B C triangle ABC ABC 的内心与 △ A ′ B ′ C ′ triangle A'B'C' ABC 的重心重合。

而在 △ A ′ B ′ C ′ triangle A'B'C' ABC 中,考虑三角形的 欧拉线 ,令外心为 O O O ,中心为 G G G ,重心为 H H H ,则 H H H O G OG OG 的延长线上,并且 ∣ H G ∣ = 2 ∣ O G ∣ |HG|=2|OG| HG=2OG 。原题中,显然 O = ( 0 , 0 ) O=(0,0) O=(0,0) ,因此若能计算出 G G G 坐标的期望,便能计算出 H H H 坐标的期望

显然 G = A ′ + B ′ + C ′ 3 G=frac{A'+B'+C'}{3} G=3A+B+C ,因此只需要枚举两个点计算 A ′ A' A 的贡献即可。

时间复杂度 O ( N 2 ) O(N^2) O(N2)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const double pi = acos(-1);
const double eps = 1e-10;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
double a[MAXN];
int main() {
	int n, l; read(n), read(l);
	for (int i = 1; i <= n; i++)
		read(a[i]);
	double ansx = 0, ansy = 0;
	for (int i = 1; i <= n; i++)
	for (int j = i + 1; j <= n; j++) {
		ansx += cos(pi * (a[i] + a[j]) / l) * (n - (j - i + 1));
		ansy += sin(pi * (a[i] + a[j]) / l) * (n - (j - i + 1));
		ansx += cos(pi * (a[i] + a[j] + l) / l) * (j - i - 1);
		ansy += sin(pi * (a[i] + a[j] + l) / l) * (j - i - 1);
	}
	double cnt = 1.0 * n * (n - 1) * (n - 2) / 6;
	printf("%.10lf %.10lfn", ansx / cnt, ansy / cnt);
	return 0;
}

Problem E. Pairing Points

首先考虑与 1 1 1 配对的点,不妨令为点 i i i

那么,我们需要对 [ 2 , i ) ∪ ( i , N ] [2,i)cup(i,N] [2,i)(i,N] 继续配对,并且,横跨两个区间的边至少要有一条,若有多条,则需要满足它们的端点之间存在单调性,即保证连线不成环。

考虑枚举最靠外侧的横跨两个区间的边 ( j , k ) (j,k) (j,k)

( j , i ) (j,i) (j,i) 之间的点或是与 j j j 连通,或是与 i i i 连通,并且存在一个分界线,考虑枚举它,记为 p p p ,同理,枚举与 i i i k k k 连通的分界线 q q q

由于 ( j , k ) (j,k) (j,k) 是最靠外侧的横跨两个区间的边,因此,对于区间 [ 2 , p ] [2,p] [2,p] 内的点,我们不再需要知晓区间 [ i , N ] [i,N] [i,N] 内的信息,从而递归为了一个点集 [ 2 , j ) ∪ ( j , p ] [2,j)cup(j,p] [2,j)(j,p] 的子问题。同理, 区间 [ q , N ] [q,N] [q,N] 内的点递归为了一个点集 [ q , k ) ∪ ( k , N ] [q,k)cup(k,N] [q,k)(k,N] 的子问题,而剩余的点则递归为了一个点集 [ p + 1 , i ) ∪ ( i , q − 1 ] [p+1,i)cup(i,q-1] [p+1,i)(i,q1] 的子问题。

由此进行动态规划,时间复杂度 O ( N 7 ) O(N^7) O(N7)

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 55;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
char s[MAXN][MAXN];
ll dp[MAXN][MAXN][MAXN];
ll getans(int l, int r, int m) {
	if (dp[l][r][m] != -1) return dp[l][r][m];
	if (l == r) return dp[l][r][m] = 1;
	if (l == m || r == m) return dp[l][r][m] = 0;
	ll &res = dp[l][r][m]; res = 0;
	for (int i = l; i <= m - 1; i++)
	for (int j = m + 1; j <= r; j++) {
		if (s[i][j] == '0') continue;
		for (int s = i; s <= m - 1; s++)
		for (int t = m + 1; t <= j; t++)
			res += getans(l, s, i) * getans(s + 1, t - 1, m) * getans(t, r, j);
	}
	return res;
}
int main() {
	int n; read(n);
	for (int i = 1; i <= n * 2; i++)
		scanf("n%s", s[i] + 1);
	memset(dp, -1, sizeof(dp));
	ll ans = 0;
	for (int i = 2; i <= n * 2; i++)
		if (s[1][i] == '1') ans += getans(2, n * 2, i);
	writeln(ans);
	return 0;
}

Problem F. Min Product Sum

考虑一个矩阵权值的组合意义,可以认为除了矩阵 A A A 外,我们还填写了矩阵 B B B ,并且,矩阵 B B B 各行各列的最大值不超过矩阵 A A A 对应行列的最小值,求总方案数。

令矩阵 B B B 各行的最大值为 X i X_i Xi ,矩阵 A A A 各列的最小值为 Y i Y_i Yi

我们希望得到一个动态规划解法,而状态实际上已经确定,应当为已经确定的行数 i i i 、列数 j j j 、数字数 k k k ,即状态数是 O ( N M K ) O(NMK) O(NMK) 级别的。因此,我们需要一个线性的转移,但是,若状态 i , j i,j i,j 都是用来描述矩阵 A A A 的,在转移时,我们就不得不枚举两维进行转移,从而超过了需要的线性复杂度。

由此,可以想到用其中一个状态描述 A A A ,另一个状态描述 B B B

d p i , j , k dp_{i,j,k} dpi,j,k 表示已经考虑了 X i ≤ k X_ileq k Xik i i i 行和 Y i ≤ k Y_ileq k Yik j j j 列,此时已经确定的数的填法总数。

转移时首先考虑枚举 X i = k + 1 X_i=k+1 Xi=k+1 的行数,对于每一个这样的行,可以在已经确定 Y i ≤ k Y_ileq k Yik j j j 列填上 ≥ k + 1 geq k+1 k+1 A A A 中的元素,并在尚未确定 Y i ≤ k Y_ileq k Yik M − j M-j Mj 列填上 ≤ k + 1 leq k+1 k+1 B B B 中的元素,并保证至少有一个填入的数 = k + 1 =k+1 =k+1

再考虑枚举 Y i = k + 1 Y_i=k+1 Yi=k+1 的列数,对于每一个这样的列,可以在已经确定 X i ≤ k X_ileq k Xik i i i 行填上 ≥ k + 1 geq k+1 k+1 A A A 中的元素,并保证至少有一个填入的数 = k + 1 =k+1 =k+1 ,并在尚未确定 X i ≤ k X_ileq k Xik N − i N-i Ni 行填上 ≤ k leq k k B B B 中的元素。

不难发现上述两个转移可以认为是转移的两个阶段,因此,只需要给状态加一维 0 / 1 0/1 0/1 即可。

预处理转移系数,时间复杂度 O ( N M K ( N + M ) ) O(NMK(N+M)) O(NMK(N+M))

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 105;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, m, k, P, dp[MAXN][MAXN][MAXN][2];
int binom[MAXN][MAXN], coef[MAXN][MAXN][MAXN][2];
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
int power(int x, int y) {
	if (y == 0) return 1;
	int tmp = power(x, y / 2);
	if (y % 2 == 0) return 1ll * tmp * tmp % P;
	else return 1ll * tmp * tmp % P * x % P;
}
int main() {
	read(n), read(m), read(k), read(P), dp[1][0][0][0] = 1;
	for (int i = 0; i <= max(n, m); i++) {
		binom[i][0] = 1;
		for (int j = 1; j <= i; j++)
			binom[i][j] = (binom[i - 1][j - 1] + binom[i - 1][j]) % P;
	}
	for (int t = 1; t <= k; t++)
	for (int i = 0; i <= m; i++) {
		for (int j = 0; j <= n; j++) {
			int tmp = (power(t, m - i) - power(t - 1, m - i) + P) % P;
			tmp = 1ll * tmp * power(k - t + 1, i) % P;
			coef[t][i][j][0] = power(tmp, j);
		}
	}
	for (int t = 1; t <= k; t++)
	for (int i = 0; i <= n; i++) {
		for (int j = 0; j <= m; j++) {
			int tmp = (power(k - t + 1, i) - power(k - t, i) + P) % P;
			tmp = 1ll * tmp * power(t, n - i) % P;
			coef[t][i][j][1] = power(tmp, j);
		}
	}
	for (int t = 1; t <= k; t++) {
		for (int i = 0; i <= n; i++)
		for (int j = 0; j <= m; j++) {
			int tmp = dp[t][i][j][0];
			for (int k = 0; i + k <= n; k++)
				update(dp[t][i + k][j][1], 1ll * tmp * binom[n - i][k] % P * coef[t][j][k][0] % P);
			tmp = dp[t][i][j][1];
			for (int k = 0; j + k <= m; k++)
				update(dp[t + 1][i][j + k][0], 1ll * tmp * binom[m - j][k] % P * coef[t][i][k][1] % P);
		}
	}
	writeln(dp[k + 1][n][m][0]);
	return 0;
}

最后

以上就是尊敬钢铁侠最近收集整理的关于【集训队作业】IOI 2020 集训队作业 试题泛做 4Atcoder Grand Contest 35Atcoder Grand Contest 36Atcoder Grand Contest 37Atcoder Grand Contest 38Atcoder Grand Contest 39的全部内容,更多相关【集训队作业】IOI内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(108)

评论列表共有 0 条评论

立即
投稿
返回
顶部