2022CCPC江苏省赛题解ACIJKL
A. PENTA KILL!
题意
称一个人连续杀了五个不同的人为五杀.给定 n ( 1 ≤ n ≤ 1000 ) n\ \ (1\leq n\leq 1000) n (1≤n≤1000)组击杀记录,判断是否有人五杀,若有,则输出"PENTA KILL!“;否则输出"SAD:(”.
思路
预处理出每个人的击杀记录, O ( n 2 ) O(n^2) O(n2)暴力找是否存在相邻的 5 5 5个不同的击杀对象.
代码 -> 2022CCPC江苏省赛-A(模拟)
const int MAXN = 1005;
int n;
vi records[MAXN];
umap<string, int> mp;
int get(string s) { // 求s离散化后的结果
if (mp.count(s)) return mp[s];
else {
mp[s] = n;
return n++;
}
}
void solve() {
CaseT{
string a,b; cin >> a >> b;
records[get(a)].push_back(get(b));
}
for (int i = 0; i < n; i++) {
if (records[i].size() < 5) continue;
for (int l = 0; l <= records[i].size() - 5; l++) {
uset<int> s;
for (int j = l; j < l + 5; j++) s.insert(records[i][j]);
if (s.size() == 5) {
cout << "PENTA KILL!";
return;
}
}
}
cout << "SAD:(";
}
int main() {
solve();
}
I. Cutting Suffix
题意
对字符串 s s s,下标从 1 1 1开始.定义 s u f f i x i suffix_i suffixi表示 s s s从第 i i i个字符开始的后缀.定义 w i j w_{ij} wij为 s u f f i x i suffix_i suffixi与 s u f f i x j suffix_j suffixj的LCP的长度.将 1 ∼ n 1\sim n 1∼n的整数分为两非空且互补的集合 T 1 T_1 T1和 T 2 T_2 T2,求 ∑ i ∈ T 1 ∑ j ∈ T 2 w i j \displaystyle\sum_{i\in T_1}\sum_{j\in T_2}w_{ij} i∈T1∑j∈T2∑wij的最小值.
第一行输入一个长度为 n ( 2 ≤ n ≤ 1 e 5 ) n\ \ (2\leq n\leq 1\mathrm{e}5) n (2≤n≤1e5)的且只包含小写英文字母的字符串 s s s.
思路
若串的所有字符都相同,取 T 1 = { n } , T 2 = { 1 , 2 , ⋯ , n − 1 } T_1=\{n\},T_2=\{1,2,\cdots,n-1\} T1={n},T2={1,2,⋯,n−1},则任意两字符串的LCP长度为 1 1 1,故答案为 n − 1 n-1 n−1.
若串至少有两种字符,将一种字符的出现位置取为 T 1 T_1 T1,另一种字符的出现位置取为 T 2 T_2 T2,则任意两字符串的LCP长度为 0 0 0,故答案为 0 0 0.
代码 -> 2022CCPC江苏省赛-I(思维)
void solve() {
string s; cin >> s;
if (s != string(s.length(), s[0])) cout << 0;
else cout << s.length() - 1;
}
int main() {
solve();
}
K. aaaaaaaaaaA heH heH nuN
题意
称前缀"nunhehheh"后接若干个(至少 1 1 1个)'a’构成的字符串是优雅的.
有 t ( 1 ≤ t ≤ 1000 ) t\ \ (1\leq t\leq 1000) t (1≤t≤1000)组测试数据.每组测试数据第一行输入一个整数 n ( 0 ≤ n ≤ 1 e 9 ) n\ \ (0\leq n\leq 1\mathrm{e}9) n (0≤n≤1e9).
对每组测试数据,输出一个长度不超过 1 e 6 1\mathrm{e}6 1e6的字符串,使得它恰有 n n n个优雅的子串.若有多组解,输出任一组.数据保证有解.
思路
注意到"nunhehheh"后接 x x x个’a’构成的字符串对答案的贡献为 2 x − 1 2^x-1 2x−1,易联想到先用 2 x 2^x 2x凑出 n n n的二进制表示,再用其他字母 + 1 +1 +1.答案从"nunhehhe"开始,即去掉前缀的最后一个’h’,在答案串后接若干个’a’,用’h’控制其后面有几个’a’是有效的.从高到低枚举 n n n的二进制数位,先在答案后接上’a’,若 n n n该数位为 1 1 1,则答案后接上’h’,并 c n t + + cnt++ cnt++.注意 n n n的第 0 0 0位无需接’a’,但若该位为 1 1 1,需更新 c n t cnt cnt,此时凑完了 2 x 2^x 2x的部分.在答案后接上 c n t cnt cnt个’h’,最后接一个’a’即凑完 + 1 +1 +1的部分.
代码 -> 2022CCPC江苏省赛-K(构造+二进制)
void solve() {
int n; cin >> n;
string ans = "nunhehhe";
int cnt = 0; // 要补的'h'的个数
for (int i = 31; i >= 1; i--) {
ans.push_back('a');
if (n >> i & 1) {
ans.push_back('h');
cnt++;
}
}
if (n & 1) cnt++; // 最后一位后不用补'a'
while (cnt--) ans.push_back('h');
ans.push_back('a');
cout << ans << endl;
}
int main() {
CaseT // 单测时注释掉该行
solve();
}
C. Jump and Treasure
题意 ( 2 s 2\ \mathrm{s} 2 s)
x x x轴上有编号 0 ∼ n 0\sim n 0∼n的 ( n + 1 ) (n+1) (n+1)个柱子,玩家从第 0 0 0号柱子出发,终点为 [ n + 1 , + ∞ ) [n+1,+\infty) [n+1,+∞)的平台,每次只能跳到柱子上,且跳跃距离不超过 p p p.除了 0 0 0号柱子,其他柱子上有价值为 a i a_i ai(可为负数)的物品,玩家到达对应的柱子后必须取走.游戏有 n n n个等级,其中第 i i i个等级中玩家只能跳到编号是 i i i的倍数的柱子上.若玩家能在等级 x x x的游戏中到达终点,求其能获得的物品价值之和的最大值.
第一行输入三个整数 n , q , p ( 2 ≤ p ≤ n ≤ 1 e 6 , 1 ≤ q ≤ 1 e 6 ) n,q,p\ \ (2\leq p\leq n\leq 1\mathrm{e}6,1\leq q\leq 1\mathrm{e}6) n,q,p (2≤p≤n≤1e6,1≤q≤1e6),分别表示柱子数、询问数、最大跳跃距离.第二行输入 n n n个整数 a 1 , ⋯ , a n ( ∣ a i ∣ ≤ 1 e 9 ) a_1,\cdots,a_n\ \ (|a_i|\leq 1\mathrm{e}9) a1,⋯,an (∣ai∣≤1e9).接下来 q q q行每行输入一个整数 x ( 1 ≤ x ≤ n ) x\ \ (1\leq x\leq n) x (1≤x≤n),表示询问若玩家能在等级 x x x的游戏中到达终点,其能获得的物品价值之和的最大值.
对每组询问,若玩家能到达终点,输出其能获得的物品价值之和的最大值;否则输出"Noob".
思路
先考察第 1 1 1个等级. d p [ i ] dp[i] dp[i]表示到达第 i i i个柱子时的最大收益,则显然有 d p [ i ] = max i − j ≤ p d p [ j ] + a [ i ] \displaystyle dp[i]=\max_{i-j\leq p}dp[j]+a[i] dp[i]=i−j≤pmaxdp[j]+a[i].暴力转移时间复杂度 O ( n 2 ) O(n^2) O(n2),会TLE,显然可用单调队列优化至 O ( n ) O(n) O(n).
对第 k k k个等级,能跳的柱子的编号为 0 , k , 2 k , ⋯ 0,k,2k,\cdots 0,k,2k,⋯,共 ( ⌊ n k ⌋ + 1 ) \left(\left\lfloor\dfrac{n}{k}\right\rfloor+1\right) (⌊kn⌋+1)个位置,则所有等级中能跳的柱子的数量为 ∑ k = 1 n ( n k + 1 ) \displaystyle\sum_{k=1}^n \left(\dfrac{n}{k}+1\right) k=1∑n(kn+1),时间复杂度 O ( n log n ) O(n\log n) O(nlogn).
代码 -> 2022CCPC江苏省赛-C(单调队列优化DP)
const int MAXN = 1e6 + 5;
int n, q, p; // 柱子数、询问数、最大跳跃距离
int a[MAXN];
ll dp[MAXN]; // dp[i]表示到达第i个柱子时的最大收益
void solve() {
cin >> n >> q >> p;
for (int i = 1; i <= n; i++) cin >> a[i];
while (q--) {
int x; cin >> x;
if (x > p) {
cout << "Noob" << endl;
continue;
}
vi pos; // 能跳的柱子的编号
for (int i = 0; i <= n; i += x) pos.push_back(i);
pos.push_back(n + 1);
deque<int> que;
que.push_back(0);
dp[0] = 0;
for (auto i : pos) {
while (que.size() && i - que.front() > p) que.pop_front(); // 超出最大跳跃距离
dp[i] = dp[que.front()] + a[i];
// 维护单调队列
while (que.size() && dp[que.back()] < dp[i]) que.pop_back();
que.push_back(i);
}
cout << dp[n + 1] << endl;
}
}
int main() {
solve();
}
J. Balanced Tree
题意 ( 1.5 s , 64 M B 1.5\ \mathrm{s},64\ \mathrm{MB} 1.5 s,64 MB)
定义二叉树 T T T是好的,如果它是空的或同时满足下列三个条件:① T T T的左子树是好的;② T T T的右子树是好的;③左右子树的节点数之差不超过 1 1 1.求包含 n n n个节点的好二叉树的个数,答案对 2 64 2^{64} 264取模.
有 t ( 1 ≤ t ≤ 1 e 6 ) t\ \ (1\leq t\leq 1\mathrm{e}6) t (1≤t≤1e6)组测试数据.每组测试数据输入一个整数 n ( 0 ≤ n < 2 64 ) n\ \ (0\leq n<2^{64}) n (0≤n<264).
思路
f [ n ] f[n] f[n]表示包含 n n n个节点的好二叉树的个数,状态转移方程 f [ n ] = { f [ n − 1 2 ] 2 , n 为奇数 2 f [ n 2 ] ⋅ f [ n 2 − 1 ] , n 为偶数 f[n]=\begin{cases}f\left[\dfrac{n-1}{2}\right]^2,n为奇数 \\ 2f\left[\dfrac{n}{2}\right]\cdot f\left[\dfrac{n}{2}-1\right],n为偶数\end{cases} f[n]=⎩ ⎨ ⎧f[2n−1]2,n为奇数2f[2n]⋅f[2n−1],n为偶数,初始条件 f [ 0 ] = 1 f[0]=1 f[0]=1.
显然答案是 2 2 2的幂次,令 g [ n ] = log 2 f [ n ] g[n]=\log_2 f[n] g[n]=log2f[n],则状态转移方程 g [ n ] = { 2 g [ n − 1 2 ] , n 为奇数 g [ n 2 ] + g [ n 2 − 1 ] + 1 , n 为偶数 g[n]=\begin{cases}2g\left[\dfrac{n-1}{2}\right],n为奇数 \\ g\left[\dfrac{n}{2}\right]+g\left[\dfrac{n}{2}-1\right]+1,n为偶数\end{cases} g[n]=⎩ ⎨ ⎧2g[2n−1],n为奇数g[2n]+g[2n−1]+1,n为偶数,初始条件 g [ 0 ] = 0 g[0]=0 g[0]=0.
显然状态只有 O ( log n ) O(\log n) O(logn)个,但时间和空间限制不允许记搜.
考虑优化,设 g [ n ] = a ⋅ g [ x ] + b ⋅ g [ x − 1 ] + c g[n]=a\cdot g[x]+b\cdot g[x-1]+c g[n]=a⋅g[x]+b⋅g[x−1]+c
= { a ( 2 g [ x − 1 2 ] ) + b ( g [ x − 1 2 ] + g [ x − 1 2 − 1 ] + 1 ) + c , x 为奇数 a ( g [ x 2 ] + g [ x 2 − 1 ] + 1 ) + b ( 2 g [ x − 1 − 1 2 ] + c ) , x 为偶数 =\begin{cases}a\left(2g\left[\dfrac{x-1}{2}\right]\right)+b\left(g\left[\dfrac{x-1}{2}\right]+g\left[\dfrac{x-1}{2}-1\right]+1\right)+c,x为奇数 \\ a\left(g\left[\dfrac{x}{2}\right]+g\left[\dfrac{x}{2}-1\right]+1\right)+b\left(2g\left[\dfrac{x-1-1}{2}\right]+c\right),x为偶数\end{cases} =⎩ ⎨ ⎧a(2g[2x−1])+b(g[2x−1]+g[2x−1−1]+1)+c,x为奇数a(g[2x]+g[2x−1]+1)+b(2g[2x−1−1]+c),x为偶数
= { ( 2 a + b ) g [ x − 1 2 ] + b ⋅ g [ x − 1 2 − 1 ] + c + b , x 为奇数 a ⋅ g [ x 2 ] + ( a + 2 b ) g [ x 2 − 1 ] + c + a , x 为偶数 =\begin{cases}(2a+b)g\left[\dfrac{x-1}{2}\right]+b\cdot g\left[\dfrac{x-1}{2}-1\right]+c+b,x为奇数 \\ a\cdot g\left[\dfrac{x}{2}\right]+(a+2b)g\left[\dfrac{x}{2}-1\right]+c+a,x为偶数\end{cases} =⎩ ⎨ ⎧(2a+b)g[2x−1]+b⋅g[2x−1−1]+c+b,x为奇数a⋅g[2x]+(a+2b)g[2x−1]+c+a,x为偶数.
初始条件 g [ n ] = 1 ⋅ g [ n ] + 0 ⋅ g [ n − 1 ] + 0 g[n]=1\cdot g[n]+0\cdot g[n-1]+0 g[n]=1⋅g[n]+0⋅g[n−1]+0,每次 x / = 2 x/=2 x/=2,按奇偶更新 a , b , c a,b,c a,b,c,最后 x = 1 x=1 x=1时 g [ n ] = c g[n]=c g[n]=c,即 a n s = 2 c m o d 2 64 ans=2^c\ \mathrm{mod}\ 2^{64} ans=2c mod 264,故 c ≥ 64 c\geq 64 c≥64时输出 0 0 0,否则输出 2 c 2^c 2c即可.
代码 -> 2022CCPC江苏省赛-J(优化递推)
ull cal(ull x, ull a, ull b, ull c) {
if (!x) return 0;
else if (x == 1) return c;
else if (x & 1) return cal(x >> 1, a * 2 + b, b, b + c);
else return cal(x >> 1, a, a + 2 * b, a + c);
}
void solve() {
ull n; cin >> n;
ull ans = cal(n, 1, 0, 0);
if (ans >= 64) cout << 0 << endl;
else cout << ((ull)1 << ans) << endl;
}
int main() {
CaseT // 单测时注释掉该行
solve();
}
L. Collecting Diamonds
题意
给定一长度为 n ( 1 ≤ s ≤ 2 e 5 ) n\ \ (1\leq s\leq 2\mathrm{e}5) n (1≤s≤2e5)的字符串 s s s,下标从 1 1 1开始.现有操作:选择一个下标 i ∈ [ 1 , n − 2 ] s . t . s i = A , s i + 1 = B , s i + 2 = C i\in[1,n-2]\ s.t.\ s_i=A,s_{i+1}=B,s_{i+2}=C i∈[1,n−2] s.t. si=A,si+1=B,si+2=C,若 i i i是奇数,则删除 s i s_i si和 s i + 2 s_{i+2} si+2;否则删除 s i + 1 s_{i+1} si+1.将剩下的字符串拼起来,并更新 n n n为新的字符串长度.求最大操作次数.
思路
注意到若删除’B’,则其附近的’A’和’C’无法再删除,且该操作会改变在其后面的字符下标的奇偶性;若删除’A’和’C’,若两端还有’A’和’C’,则会与中间剩下的’B’拼起来,可作为下一次的操作对象.显然应在删除’B’前删除尽量多的’A’和’C’.因一个’B’与一对’A’和’C’对应,记录当前删除的’B’的个数 B c n t Bcnt Bcnt即可求出操作次数.
为防止时间复杂度变为 O ( n 2 ) O(n^2) O(n2),实现时不进行操作后的字符串拼接和重新赋下标的操作,而通过下标的奇偶性和之前是否删除过’B’(即改变后面元素的奇偶性)来判断本次操作删除的是’A’和’C’还是’B’,显然这样并不影响操作次数.
代码 -> 2022CCPC江苏省赛-L(贪心)
const int MAXN = 2e5 + 5;
char s[MAXN];
void solve() {
cin >> s + 1;
int n = strlen(s + 1);
int Bcnt = 0; // 记录当前删了多少个'B'
int ans = 0;
for (int i = 1; i <= n; i++) {
if (s[i] == 'B') {
int ACcnt = 1; // 记录最多可删多少个'A'和'C',作为半径故从1开始
while (i - ACcnt >= 1 && i + ACcnt <= n && s[i - ACcnt] == 'A' && s[i + ACcnt] == 'C') ACcnt++;
if (--ACcnt == 0) continue; // 附近不是"ABC",注意减掉一开始多的1
if (i - 1 & 1) { // 注意是看下标(i-1)的奇偶性
ACcnt--; // 若之前未删过'B',则本次操作删除'A'和'C'
ans++; // 此次删除可能是删除'A'和'C'或下面的删除'B',取决于之前是否删过'B'
if (!ACcnt) { // 'A'和'C'删完了
if(Bcnt) Bcnt++; // 若之前删过'B',则本次操作删除'B'
continue;
}
}
// (i-1)是偶数
Bcnt++; // 还有剩下的"ABC",还能再删一个'B'
ans += min(ACcnt, Bcnt); // 一个'B'与一对'A'和'C'对应
}
}
cout << ans;
}
int main() {
solve();
}