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+10N≡0 (mod 4)N≡1 (mod 4)N≡2 (mod 4)N≡3 (mod 4)
因此,对于 N ≡ 3 ( m o d 4 ) Nequiv3 (mod 4) N≡3 (mod 4) 的情况,可以直接仿照样例。
对于 N ≡ 1 ( m o d 4 ) Nequiv1 (mod 4) N≡1 (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) N≡0 (mod 4) 的情况,可以直接仿照样例排列 1 , 2 , … , N − 1 1,2,dots,N-1 1,2,…,N−1 ,我们需要使得异或和为 N N N 的两个数相邻,令 N N N 的最高位为 X X X ,可以交换 X − 1 , X ⊕ N X-1,Xoplus N X−1,X⊕N ,使得 X ⊕ N , X Xoplus N,X X⊕N,X 排在相邻的位置。
对于 N ≡ 2 ( m o d 4 ) Nequiv2 (mod 4) N≡2 (mod 4) 的情况,可以直接仿照样例排列 2 , 3 , … , N − 1 2,3,dots,N-1 2,3,…,N−1 ,我们需要使得异或和为 1 , N 1,N 1,N 的两个数相邻,令 N N N 的最高位为 X X X ,可以交换 X − 1 , X ⊕ N X-1,Xoplus N X−1,X⊕N ,使得 X ⊕ N , X Xoplus N,X X⊕N,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 l−1 处的数字会被计算 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,i−1,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 i−2,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)
(i−1,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
i−K≥i−2∗k+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})
(i−1,j,k,lj,lk)⇒(i,j+1,k,lj,max{lk,i−2∗j+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)
(i−1,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′,j≥1 ,而
l
j
<
l
j
′
l_j<l'_j
lj<lj′ ,因此
A
l
j
′
,
j
≤
1
A_{l'_j,j}leq1
Alj′,j≤1 ,从而
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
klj′≥j,klj′′<j ,又因为数列
(
l
′
,
k
′
)
(l',k')
(l′,k′) 是一个标准的数列,
k
l
j
′
′
≠
j
−
1
k'_{l'_j}ne j-1
klj′′=j−1 ,并且
l
j
−
1
=
l
j
−
1
′
l_{j-1}=l'_{j-1}
lj−1=lj−1′ ,因此数列
(
l
,
k
)
(l,k)
(l,k) 生成的网格在
A
j
−
1
,
l
j
′
A_{j-1,l'_j}
Aj−1,lj′ 处大于数列
(
l
′
,
k
′
)
(l',k')
(l′,k′) 生成的网格在
A
j
−
1
,
l
j
′
A_{j-1,l'_j}
Aj−1,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=0∑min{N,M}(−1)i×(iN)×(iM)×i!×(N+1)M−i×(M+1)N−i
时间复杂度 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,a−1] 的向后的边。
不难发现在状态中计入 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 a≤b≤c。
考虑选定一个 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 a≤b,a≤c 。
仍然设 a ≤ b ≤ c aleq bleq c a≤b≤c ,考虑删除一些 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 a−1 段字符中仅由一个 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 x≥y 。
考虑先选取所有的 A A A ,若 x ≥ y xgeq y x≥y ,则找到的一定是最优解,否则记 d = y − x d=y-x d=y−x ,删去 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,… (a0≤a1≤a2≤…) ,那么方案数即为 ∏ i ( a i − i ) prod_{i}(a_i-i) ∏i(ai−i) 。
记 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 i≥N 则为 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 ≤N−1 的 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×2K−1 取最大值。
使得 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 ri−li+1≥L ,并且以 ⌊ r i − l i + 1 L ⌋ lfloorfrac{r_i-l_i+1}{L}rfloor ⌊Lri−li+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,…,L−1 个元素显然不能作为区间的右端点,不对 Y i Y_i Yi 产生贡献; L , L + 1 , … , 2 L − 1 L,L+1,dots,2L-1 L,L+1,…,2L−1 个元素作为区间的右端点可以产生一个 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,…,3L−1 个元素作为区间的右端点可以产生两个 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)=ex−i=1∏N(epix−1−pix−2pi2x2−⋯−(Bi−1)!piBi−1xBi−1)
令
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)=ySA−i=1∏N(yAi−1−pix−2pi2x2−⋯−(Bi−1)!piBi−1xBi−1)
暴力展开 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}}
1−SAic
考虑形如
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,i∈N ,计算
S
=
∑
k
=
0
∞
p
k
×
k
i
S=sum_{k=0}^{infty}p^ktimes k^i
S=k=0∑∞pk×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=0∑∞pk+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)
(1−p)S=[i=0]+k=0∑∞pk+1×((k+1)i−ki)
将 ( 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
,,Pi,Qi,i 互不相同:同时改变
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
P↔Q
(
3
)
(3)
(3) 、
S
→
Q
Srightarrow Q
S→Q
(
4
)
(4)
(4) 、
P
→
T
Prightarrow T
P→T
(
5
)
(5)
(5) 、
P
→
Q
Prightarrow Q
P→Q
时间复杂度 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' △A′B′C′ 的重心重合。
而在 △ A ′ B ′ C ′ triangle A'B'C' △A′B′C′ 中,考虑三角形的 欧拉线 ,令外心为 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∣=2∣OG∣ 。原题中,显然 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,q−1] 的子问题。
由此进行动态规划,时间复杂度 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 Xi≤k 的 i i i 行和 Y i ≤ k Y_ileq k Yi≤k 的 j j j 列,此时已经确定的数的填法总数。
转移时首先考虑枚举 X i = k + 1 X_i=k+1 Xi=k+1 的行数,对于每一个这样的行,可以在已经确定 Y i ≤ k Y_ileq k Yi≤k 的 j j j 列填上 ≥ k + 1 geq k+1 ≥k+1 的 A A A 中的元素,并在尚未确定 Y i ≤ k Y_ileq k Yi≤k 的 M − j M-j M−j 列填上 ≤ 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 Xi≤k 的 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 Xi≤k 的 N − i N-i N−i 行填上 ≤ 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内容请搜索靠谱客的其他文章。
发表评论 取消回复