A.Good Permutation 2(贪心)
题意:
给你一个正整数 N N N和一个由 M M M个正整数 A = ( A 1 , A 2 , … , A M ) A=(A_{1},A_{2}, \dots,A_{M}) A=(A1,A2,…,AM)组成的序列。
在这里, A A A的所有元素都是介于 1 1 1和 N N N之间的不同整数。
对于所有整数 i i i( 1 ≤ i ≤ M 1\leq i\leq M 1≤i≤M)满足以下条件时, ( 1 , 2 , … , N ) (1,2,\dots,N) (1,2,…,N)的排列 P = ( P 1 , P 2 , … , P N ) P=(P_{1},P_{2},\dots,P_{N}) P=(P1,P2,…,PN)称为好排列:
- P P P的任何连续子序列都不是 ( 1 , 2 , … , A i ) (1,2,\dots,A_{i}) (1,2,…,Ai)的置换。
判断是否存在好的排列,如果存在,求字典序最小的好的排列。
分析:
本题考虑贪心思维。
将答案数组赋初值为 1 , 2 , . . . , n 1,2,...,n 1,2,...,n,将约束条件 a i a_i ai从小到大排序。
对于每一个 x ∈ a x\in a x∈a,因为每一个排列必须包含 1 1 1,所以就让 r e s 1 , r e s 2 . . . , r e s x res_1,res_2...,res_x res1,res2...,resx不是一个排列。
每次交换 r e s x res_x resx和 r e s x + 1 res_{x+1} resx+1可以保证符合要求,且字典序最小。
考虑无解的情况。
- 如果 a = n a=n a=n,则对于整个答案排列,一定是一个不符合这一要求的。
- 如果 a = 1 a=1 a=1,则对于答案中子序列{1},同样无法满足。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1000005;
int a[N], b[N];
int main() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= m; i++)
cin >> a[i];
sort(a + 1, a + 1 + m);
if (a[m] == n || a[1] == 1) {
cout << "-1" << endl;
return 0;
}
set<int> st;
for (int i = 1; i <= n; i++)
st.insert(i);
for (int i = 1; i <= m; i++) {
b[a[i]] = a[i] + 1;
st.erase(a[i] + 1);
}
for (int i = 1; i <= n; i++) {
if (b[i] == 0) {
b[i] = *st.begin();
st.erase(st.begin());
}
}
for (int i = 1; i <= n; i++)
cout << b[i] << " ";
cout << endl;
return 0;
}
B.1 + 6 = 7(数学)
题意:
给你正整数 A 1 , A 2 , A 3 A_{1},A_{2},A_{3} A1,A2,A3。求满足以下所有条件的正整数 ( X 1 , X 2 , X 3 ) (X_{1},X_{2},X_{3}) (X1,X2,X3)元组的个数,答案对 998244353 998244353 998244353取模。
- X 1 X_{1} X1是一个十进制符号为 A 1 A_{1} A1位的正整数。
- X 2 X_{2} X2是一个十进制符号为 A 2 A_{2} A2位的正整数。
- X 3 X_{3} X3是一个十进制符号为 A 3 A_{3} A3位的正整数。
- X 1 + X 2 = X 3 X_{1}+X_{2}=X_{3} X1+X2=X3。
每个输入给出 T T T个测试样例,请逐一求解。
分析:
令 f ( B 1 , B 2 , B 3 ) f(B_{1},B_{2},B_{3}) f(B1,B2,B3)表示下面证明的答案。
考虑有多少对整数 ( Y 1 , Y 2 ) (Y_{1},Y_{2}) (Y1,Y2)满足以下所有条件?
- 1 0 B 1 ≤ Y 1 10^{B_{1}}\leq Y_{1} 10B1≤Y1
- 1 0 B 2 ≤ Y 2 10^{B_{2}}\leq Y_{2} 10B2≤Y2
- Y 1 + Y 2 < 1 0 B 3 Y_{1}+Y_{2}\lt 10^{B_{3}} Y1+Y2<10B3
我们要找出满足以下所有条件的整数对 ( X 1 , X 2 ) (X_{1},X_{2}) (X1,X2)的个数:
- 1 0 A 1 − 1 ≤ X 1 10^{A_{1}-1}\leq X_{1} 10A1−1≤X1
- 1 0 A 2 − 1 ≤ X 2 10^{A_{2}-1}\leq X_{2} 10A2−1≤X2
- X 1 + X 2 < 1 0 A 3 X_{1}+X_{2}\lt 10^{A_{3}} X1+X2<10A3
- 1 0 A 1 ≤ X 1 10^{A_{1}}\leq X_{1} 10A1≤X1不成立。
- 1 0 A 2 ≤ X 2 10^{A_{2}}\leq X_{2} 10A2≤X2不成立。
- X 1 + X 2 < 1 0 A 3 − 1 X_{1}+X_{2}\lt 10^{A_{3}-1} X1+X2<10A3−1不成立。
利用包含-排除原则,这个问题的答案可以用
f
f
f表示如下:
∑
i
=
0
1
∑
j
=
0
1
∑
k
=
0
1
f
(
A
1
−
i
,
A
2
−
j
,
A
3
−
k
)
(
−
1
)
i
+
j
+
k
\sum_{i=0}^{1}\sum_{j=0}^{1}\sum_{k=0}^{1}f(A_{1}-i,A_{2}-j,A_{3}-k)(-1)^{i+j+k}
i=0∑1j=0∑1k=0∑1f(A1−i,A2−j,A3−k)(−1)i+j+k
因此,只需实现一个能快速计算 f ( B 1 , B 2 , B 3 ) f(B_{1},B_{2},B_{3}) f(B1,B2,B3)的函数即可。对于 f ( B 1 , B 2 , B 3 ) f(B_{1},B_{2},B_{3}) f(B1,B2,B3),以下条件成立:
- 若 B 3 ≤ max ( B 1 , B 2 ) B_{3}\leq\max(B_{1},B_{2}) B3≤max(B1,B2),则 f ( B 1 , B 2 , B 3 ) = 0 f(B_{1},B_{2},B_{3})=0 f(B1,B2,B3)=0。
- 若 B 3 > max ( B 1 , B 2 ) B_{3}\gt \max(B_{1},B_{2}) B3>max(B1,B2),则 f ( B 1 , B 2 , B 3 ) = ( 1 0 B 3 − 1 0 B 1 − 1 0 B 2 ) ( 1 0 B 3 − 1 0 B 1 − 1 0 B 2 + 1 ) 2 f(B_{1},B_{2},B_{3})= \dfrac{(10^{B_{3}}-10^{B_{1}}-10^{B_{2}})(10^{B_{3}}-10^{B_{1}}-10^{B_{2}}+1)}{2} f(B1,B2,B3)=2(10B3−10B1−10B2)(10B3−10B1−10B2+1)。
由上可知, f ( B 1 , B 2 , B 3 ) f(B_{1},B_{2},B_{3}) f(B1,B2,B3)可在 O ( log ( B 3 ) ) O(\log(B_{3})) O(log(B3))时间内计算,因此每个测试样例可在 O ( log ( A 3 ) ) O(\log(A_{3})) O(log(A3))时间内解决此问题。
代码:
#include<bits/stdc++.h>
using namespace std;
#include<atcoder/modint>
using mint = atcoder::modint998244353;
mint ten = 10;
mint f(int b1, int b2, int b3){
if (max(b1, b2) >= b3) return 0;
mint tmp = ten.pow(b3) - ten.pow(b1) - ten.pow(b2);
return tmp * (tmp + 1) / 2;
}
int main(){
int T;
cin >> T;
while(T--){
int a1, a2, a3;
cin >> a1 >> a2 >> a3;
mint ans = 0, pm = 1;
for (int i = 0; i < 2; i++){
for (int j = 0; j < 2; j++){
for (int k = 0; k < 2; k++){
ans += pm * f(a1 - i, a2 - j, a3 - k);
pm *= -1;
}
pm *= -1;
}
pm *= -1;
}
cout << ans.val() << endl;
}
}
C.Sum of Abs 2(数学、动态规划)
题意:
问题陈述
给你正整数 N N N和 L L L以及长度为 N N N的正整数序列 A = ( A 1 , A 2 , … , A N ) A=(A_{1},A_{2},\dots,A_{N}) A=(A1,A2,…,AN)。
就每个 i = 1 , 2 , … , N i=1,2,\dots,N i=1,2,…,N回答下面的问题:
判断是否存在一个由 L L L个非负整数 B = ( B 1 , B 2 , … , B L ) B=(B_{1},B_{2},\dots,B_{L}) B=(B1,B2,…,BL)组成的序列,使得 ∑ j = 1 L − 1 ∑ k = j + 1 L ∣ B j − B k ∣ = A i \displaystyle\sum_{j=1}^{L-1}\sum_{k=j+1}^{L}|B_{j}-B_{k}|=A_{i} j=1∑L−1k=j+1∑L∣Bj−Bk∣=Ai。如果存在,求该序列 max ( B ) \max(B) max(B)的最小值 B B B。
分析:
本题"绝对值之和"指的是以下公式:
∑ j = 1 L − 1 ∑ k = j + 1 L ∣ B j − B k ∣ \sum_{j=1}^{L - 1}\sum_{k = j + 1} ^ {L} |B_{j} - B_{k}| j=1∑L−1k=j+1∑L∣Bj−Bk∣
当存在 B B B时,能够最小化 max ( B ) \max(B) max(B)的 B B B满足以下两个条件:
- B B B按升序排序。
- B 1 = 0 B_{1}=0 B1=0.
第一个条件成立的原因是,绝对值之和与 B B B的顺序无关。第二个条件成立是因为如果 B B B中的所有元素都是正数,那么从所有元素中减去 1 1 1就可以减少 max ( B ) \max(B) max(B)而不改变绝对值之和。
当 B B B按升序排序时,绝对值之和可以表示如下:
∑ j = 1 L − 1 ∑ k = j + 1 L ∣ B j − B k ∣ = ∑ j = 1 L − 1 ∑ k = j + 1 L ( B k − B j ) = ∑ k = 1 L − 1 k ( L − k ) ( B k + 1 − B k ) \sum_{j=1}^{L-1}\sum_{k=j+1}^{L}|B_{j}-B_{k}|=\sum_{j=1}^{L-1}\sum_{k=j+1}^{L}(B_{k}-B_{j})=\sum_{k=1}^{L-1}k(L-k)(B_{k+1}-B_{k}) j=1∑L−1k=j+1∑L∣Bj−Bk∣=j=1∑L−1k=j+1∑L(Bk−Bj)=k=1∑L−1k(L−k)(Bk+1−Bk)
这个变换是正确的,因为满足 1 ≤ b < a ≤ L 1\leq b\lt a\leq L 1≤b<a≤L 的整数 a , b a,b a,b为 B a − B b = ( B a − B a − 1 ) + ( B a − 1 + B a − 2 ) + ⋯ + ( B b + 1 − B b ) B_{a}-B_{b}=(B_{a}-B_{a-1})+(B_{a-1}+B_{a-2})+\cdots+(B_{b+1}-B_{b}) Ba−Bb=(Ba−Ba−1)+(Ba−1+Ba−2)+⋯+(Bb+1−Bb),满足 b ≤ k b\leq k b≤k和 k + 1 ≤ a k+1\leq a k+1≤a的整数对 ( a , b ) (a,b) (a,b)的个数为 k ( L − k ) k(L-k) k(L−k)。
如果我们让 C k = B k + 1 − B k C_{k}=B_{k+1}-B_{k} Ck=Bk+1−Bk,那么当 B B B按升序排序和 B 1 = 0 B_{1}=0 B1=0时,下面的情况成立:
- 0 ≤ C k 0\leq C_{k} 0≤Ck.
- 绝对值之和等于 ∑ k = 1 L − 1 k ( L − k ) C k \displaystyle\sum_{k=1}^{L-1}k(L-k)C_{k} k=1∑L−1k(L−k)Ck。
- max ( B ) = B L = ∑ k = 1 L − 1 C k \displaystyle\max(B)=B_{L}=\sum_{k=1}^{L-1}C_{k} max(B)=BL=k=1∑L−1Ck.
因此,我们需要解决下面的问题:
确定是否存在长度为 L − 1 L-1 L−1的非负整数序列 C C C,使得 ∑ k = 1 L − 1 k ( L − k ) C k = A i \displaystyle\sum_{k=1}^{L-1}k(L-k)C_{k}=A_{i} k=1∑L−1k(L−k)Ck=Ai,如果存在,求这样的 C C C的最小和。
这个问题可以用动态规划的思想来解决,方法与背包问题相同,可以同时计算所有 i i i的和。
由于 C k = 0 C_{k}=0 Ck=0对于满足 max ( A ) < k ( L − k ) \max(A)\lt k(L-k) max(A)<k(L−k) 的 k k k总是成立的,解法的空间复杂度为 O ( max ( A ) ) O(\max(A)) O(max(A)),时间复杂度为 O ( N + max ( A ) max ( A ) ) O(N+\max(A)\sqrt{\max(A)}) O(N+max(A)max(A))。
代码:
#include<bits/stdc++.h>
using namespace std;
int main() {
int N, L;
cin >> N >> L;
const int S = 200200;
vector<int> dp(S, S);
dp[0] = 0;
for (int k = 1; k < L; k++) {
int w = k * (L - k);
if (S < w)
break;
for (int i = 0; i + w < S; i++) {
dp[i + w] = min(dp[i + w], dp[i] + 1);
}
}
while (N--) {
int a;
cin >> a;
cout << (dp[a] == S ? -1 : dp[a]) << endl;
}
return 0;
}
D.Delete Range Mex(动态规划)
题意:
给你一个正整数 N N N和一个由 M M M非负整数 A = ( A 1 , A 2 , … , A M ) A=(A_{1},A_{2},\dots,A_{M}) A=(A1,A2,…,AM)组成的序列。
在这里, A A A的所有元素都是介于 0 0 0和 N − 1 N-1 N−1之间的不同整数。
求满足以下条件的 ( 0 , 1 , … , N − 1 ) (0,1,\dots,N-1) (0,1,…,N−1)的排列 P P P对 998244353 998244353 998244353取模的个数。
- 将序列
B
=
(
B
1
,
B
2
,
…
,
B
N
)
B=(B_{1},B_{2},\dots,B_{N})
B=(B1,B2,…,BN)初始化为
P
P
P后,重复下面的操作若干次,可以得到
B
=
A
B = A
B=A:
- 选择 l l l和 r r r,使得 1 ≤ l ≤ r ≤ ∣ B ∣ 1\leq l\leq r\leq|B| 1≤l≤r≤∣B∣,如果 B B B中包含 m e x ( { B l , B l + 1 , … , B r } ) \mathrm{mex}(\{B_{l},B_{l+1},\dots,B_{r}\}) mex({Bl,Bl+1,…,Br}),则将其从 B B B中删除。
m e x ( X ) \mathrm{mex}(X) mex(X):对于非负整数的有限集合 X X X来说, m e x ( X ) \mathrm{mex}(X) mex(X)的定义是不在 X X X中的最小非负整数。
分析:
首先,让我们考虑是否有可能将 B = P B=P B=P转换为 B = A B=A B=A。由于 B B B中没有重复的元素,一旦从 B B B中删除了一个整数 x x x,之后就不可能再删除任何大于或等于 x x x的数字了。因此,我们需要按降序移除 A A A中不包含的元素。
此外,从 B B B中移除 x x x时,我们应该选择不包含 x x x且其 m e x \mathrm{mex} mex为 x x x的最大区间。由于 B B B中没有重复的元素,因此最多有两个候选区间,我们应该选择包含所有小于 x x x的元素的区间。
最后,我们需要计算满足以下所有条件的 P P P的个数:
- A A A作为 P P P的子序列存在。
- 对于所有不包含在
A
A
A中且介于
0
0
0和
N
−
1
N-1
N−1之间的整数
x
x
x,以下条件之一成立:
- 从 0 0 0到 x − 1 x-1 x−1的所有整数的索引都小于 x x x的索引。
- 从 0 0 0到 x − 1 x-1 x−1的所有整数的索引都大于 x x x的索引。
我们考虑按升序插入不包含在 A A A中的整数来满足条件。
第一个条件总是满足的。
对于第二个条件,当插入一个整数 x x x时,我们应将其插入最左边小于 x x x的整数的左边,或插入最右边小于 x x x的整数的右边。
在此基础上,我们考虑动态规划。
设 d p [ i ] [ l ] [ r ] dp[i][l][r] dp[i][l][r]是插入 i i i以内整数的方法数,使得最左边整数的左边最多有 l l l个整数 i i i,最右边整数的右边最多有 r r r个整数 i i i。
问题的答案是 d p [ N − 1 ] [ 0 ] [ 0 ] dp[N-1][0][0] dp[N−1][0][0]。
如果 0 0 0包含在 A A A中,则使用 k k k初始化 d p [ 0 ] [ k − 1 ] [ M − k ] = 1 dp[0][k-1][M-k]=1 dp[0][k−1][M−k]=1,从而得到 A k = 0 A_k=0 Ak=0。
如果 0 0 0不包含在 A A A中,则初始化 d p [ 0 ] [ i ] [ M − i ] = 1 dp[0][i][M-i]=1 dp[0][i][M−i]=1为 i = 0 , 1 , … , M i=0,1,\dots,M i=0,1,…,M。
依次计算 i = 1 , 2 , … , N − 1 i=1,2,\dots,N-1 i=1,2,…,N−1的 d p [ i ] dp[i] dp[i]。
当 i i i包含在 A A A中时
使用 k k k这样的 A k = i A_k=i Ak=i,对所有 l , r l,r l,r进行如下更新:
d p [ i ] [ min ( l , k − 1 ) ] [ min ( r , M − k ) ] + = d p [ i − 1 ] [ l ] [ r ] dp[i][\min(l,k-1)][\min(r,M-k)]+=dp[i-1][l][r] dp[i][min(l,k−1)][min(r,M−k)]+=dp[i−1][l][r]
更新只需 O ( M 2 ) O(M^2) O(M2)时间。
当 i i i不包括在 A A A中时
以下条件成立:
d p [ i ] [ l ] [ r ] = ∑ L = l M d p [ i − 1 ] [ L ] [ r ] + ∑ R = r M d p [ i − 1 ] [ l ] [ R ] dp[i][l][r]=\sum_{L=l}^{M}dp[i-1][L][r] +\sum_{R=r}^{M}dp[i-1][l][R] dp[i][l][r]=L=l∑Mdp[i−1][L][r]+R=r∑Mdp[i−1][l][R]
如果用简单的方法计算,时间复杂度为 O ( M 3 ) O(M^3) O(M3),但使用累积和方法,计算时间为 O ( M 2 ) O(M^2) O(M2)。
因此,我们的解决方案的总体时间复杂度为
O
(
N
M
2
)
O(NM^2)
O(NM2)。
使用二项式系数直接计算
d
p
[
m
i
n
(
A
)
]
dp[min(A)]
dp[min(A)]可以减少需要考虑的表的范围,从而使计算速度提高一个常数量级。
代码:
#include<bits/stdc++.h>
#include<atcoder/modint>
using namespace std;
using mint = atcoder::modint;
int main() {
int N, M;
cin >> N >> M;
vector<int> B(N, -1);
for (int i = 0; i < M; i++) {
int a;
cin >> a;
B[a] = i;
}
vector<mint> fact(N + 1, 1), fact_inv(N + 1, 1);
for (int i = 1; i <= N; i++) fact[i] = fact[i - 1] * i;
fact_inv[N] = fact[N].inv();
for (int i = N; i > 0; i--) fact_inv[i - 1] = fact_inv[i] * i;
auto Binom = [&](int a, int b) -> mint {
if (N < a || a < b || b < 0) return 0;
return fact[a] * fact_inv[b] * fact_inv[a - b];
};
int X = 0;
while (B[X] == -1) X++;
int L = B[X], R = M - 1 - B[X];
vector dp(L + 1, vector<mint>(R + 1));
for (int i = 0; i <= L; i++)
for (int j = 0; j <= R; j++) {
int Y = X;
if (i != L) Y--;
if (j != R) Y--;
int Z = L - i + R - j + 1;
dp[i][j] = Binom(Y + Z, Z);
}
for (int idx = X + 1; idx < N; idx++) {
int c = B[idx];
if (c == -1) {
vector n_dp(L + 1, vector<mint>(R + 1));
for (int i = 0; i <= L; i++) {
mint tmp = 0;
for (int j = R; j >= 0; j--) {
tmp += dp[i][j];
n_dp[i][j] = tmp;
}
}
for (int i = L; i >= 0; i--) {
for (int j = 0; j <= R; j++) {
if (i) dp[i - 1][j] += dp[i][j];
n_dp[i][j] += dp[i][j];
}
}
swap(n_dp, dp);
} else if (c < L) {
for (int i = c + 1; i <= L; i++) {
for (int j = 0; j <= R; j++) {
dp[c][j] += dp[i][j];
}
}
L = c;
} else if (M - 1 - c < R) {
c = M - 1 - c;
for (int i = 0; i <= L; i++) {
for (int j = c + 1; j <= R; j++) {
dp[i][c] += dp[i][j];
}
}
R = c;
}
}
cout << (dp[0][0] * (mint(2)).pow(max(X - 1, 0))).val() << endl;
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。