这场还是有点东西的,这几个题都不难,但是要做对还是比较麻烦的。B是一个简单的博弈,C是鸽巢原理,D1是推一个结论,D2是一个dp,初始化比较烦人。
A. Turtle and Good Strings
题面:
乌龟认为,如果存在一串字符串 t 1 , t 2 , … , t k t_1, t_2, \ldots, t_k t1,t2,…,tk ( k k k 是任意整数),那么字符串 s s s 就是一个好字符串:
- k ≥ 2 k \ge 2 k≥2 .
- s = t 1 + t 2 + … + t k s = t_1 + t_2 + \ldots + t_k s=t1+t2+…+tk ,其中 + + + 表示连接操作。例如, abc = a + bc \texttt{abc} = \texttt{a} + \texttt{bc} abc=a+bc 。
- 对于所有 1 ≤ i < j ≤ k 1 \le i \lt j \le k 1≤i<j≤k , t i t_i ti 的第一个字符不等于 1 ≤ i < j ≤ k 1 \le i \lt j \le k 1≤i<j≤k 。不等于 t j t_j tj 的最后一个字符。
乌龟得到一个由小写拉丁字母组成的字符串 s s s 。请告诉他 s s s 是否是一个好字符串!
思路:
其实发现如果拆的字符串多的话,比较的字符就会更多,更不容易满足条件,所以我们就拆成两个就可以了。
而且无论中间怎么拆,第一个字符串的第一个字符一定会和最后一个字符串的最后一个字符进行比较,其他的不比较,所以我们只需要第一个字符串的第一个字符和最后一个字符串的最后一个字符比较一下就可以了。
code:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int T,n;
string s;
int main(){
cin>>T;
while(T--){
cin>>n>>s;
if(s[0]==s[n-1])puts("NO");
else puts("YES");
}
return 0;
}
B. Turtle and Piggy Are Playing a Game 2
题面:
小乌龟和小猪在玩一个数列游戏。他们得到一个数列 a 1 , a 2 , … , a n a_1, a_2, \ldots, a_n a1,a2,…,an ,小乌龟先来。小乌龟和小猪轮流进行(所以,小乌龟做第一轮,小猪做第二轮,小乌龟做第三轮,等等)。
游戏过程如下
- 假设当前序列的长度为 m m m 。如果是 m = 1 m = 1 m=1 ,游戏结束。
- 如果游戏没有结束,轮到小乌龟,那么小乌龟必须选择一个整数 i i i ,使得 1 ≤ i ≤ m − 1 1 \le i \le m - 1 1≤i≤m−1 ,将 a i a_i ai 设置为 max ( a i , a i + 1 ) \max(a_i, a_{i + 1}) max(ai,ai+1) ,并删除 a i + 1 a_{i + 1} ai+1 。
- 如果游戏没有结束,轮到小猪,那么小猪必须选择一个整数 i i i ,使得 1 ≤ i ≤ m − 1 1 \le i \le m - 1 1≤i≤m−1 ,将 a i a_i ai 设置为 min ( a i , a i + 1 ) \min(a_i, a_{i + 1}) min(ai,ai+1) ,并删除 a i + 1 a_{i + 1} ai+1 。
小乌龟希望最后最大化 a 1 a_1 a1 的值,而小猪希望最后最小化 a 1 a_1 a1 的值。如果双方都以最优方式下棋,求最后 a 1 a_1 a1 的值。
思路:
这种放在 B B B 的位置上的题一般出不到什么牛逼高大上的博弈模型,或者是SG函数,所以我们就以最简单的博弈思想来看就行了:要么自己养出最大最小值,要么阻止对方得到最小最大值。
我们随便挑选一个人的视角,比如我们代入小乌龟的视角,想要最大化最后的值,那么我们有两个策略:
- 每次删掉最大值旁边的数,保留最小值
- 删掉最小值,让对方得不到最小值。
其实我们发现第一个策略是不可行的,因为对方只要采取第二种策略,我们留着的最大值就消失了,所以这个博弈其实就是让两个人互相删掉对方的最大最小值,最后剩下的其实就是中值。
code:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=1e5+5;
int T,n,a[maxn];
int main(){
cin>>T;
while(T--){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
sort(a+1,a+n+1);
cout<<a[n/2+1]<<"\n";
}
return 0;
}
C. Turtle and Good Pairs
题面:
乌龟会给出一个由小写拉丁字母组成的字符串 s s s 。
乌龟认为一对整数 ( i , j ) (i, j) (i,j) ( 1 ≤ i < j ≤ n 1 \le i < j \le n 1≤i<j≤n ) 是令人愉快的一对,当且仅当存在一个整数 k k k ,使得 i ≤ k < j i \le k < j i≤k<j 和下面两个条件都成立:
- s k ≠ s k + 1 s_k \ne s_{k + 1} sk=sk+1 ;
- s k ≠ s i s_k \ne s_i sk=si 或 s k + 1 ≠ s j s_{k + 1} \ne s_j sk+1=sj 。
此外,乌龟认为一对整数 ( i , j ) (i, j) (i,j) ( 1 ≤ i < j ≤ n 1 \le i < j \le n 1≤i<j≤n )是一对好数,当且仅当 s i = s j s_i = s_j si=sj 或 ( i , j ) (i, j) (i,j) 是一对令人愉快的数。
乌龟想给字符串 s s s 重新排序,以便使好的配对数最大。请帮帮他!
思路:
鸽巢原理好题。
这题其实看明白的话,其实就是在说像是aaabb这种,前面一部分都是一个字符,后面一部分都是另一个字符的子串不算数。所以我们尽可能把相同的字符隔开,变成像是ababa这种形式就好了。
我们把奇数位置看成鸽巢,把字符看成鸽子,其实就是往鸽巢里塞鸽子(塞满奇数位置剩下的依次放入偶数位置即可),这就是一个鸽巢原理。
所以我们把所有字符和它们出现的次数处理出来,按照出现次数排序。然后将字符一个一个依次放入奇数位置和偶数位置即可。
code:
#include <iostream>
#include <cstdio>
#include <map>
#include <cstring>
using namespace std;
int T,n;
string s;
int main(){
cin>>T;
while(T--){
cin>>n>>s;
map<char,int> mp;
multimap<int,char> rmp;
for(int i=0;i<n;i++)mp[s[i]]++;
for(auto [ch,cnt]:mp)rmp.insert(pair<int,char>(cnt,ch));
for(int i=0;i<n;i+=2){
char ch=rmp.begin()->second;
// cout<<ch<<" "<<mp.size()<<" "<<rmp.size()<<endl;
s[i]=ch;
mp[ch]--;
if(mp[ch]==0){
mp.erase(ch);
rmp.erase(rmp.begin());
}
}
for(int i=1;i<n;i+=2){
char ch=rmp.begin()->second;
s[i]=ch;
mp[ch]--;
if(mp[ch]==0){
mp.erase(ch);
rmp.erase(rmp.begin());
}
}
cout<<s<<endl;
}
return 0;
}
D1. Turtle and a MEX Problem (Easy Version)
题面:
两个版本是不同的问题。在这个版本的问题中,你可以两次或多次选择同一个整数。只有两个版本的问题都解决了,你才能进行破解。
有一天,乌龟在玩 n n n 个序列。假设第 i i i 个序列的长度为 l i l_i li 。那么第 i i i 个序列是 a i , 1 , a i , 2 , … , a i , l i a_{i, 1}, a_{i, 2}, \ldots, a_{i, l_i} ai,1,ai,2,…,ai,li .
小猪在乌龟玩耍的时候给乌龟出了一道难题。问题的陈述是
- 一开始有一个非负整数 x x x 。小乌龟要对这个整数进行任意次数(可能是零)的运算。
- 在每次运算中,乌龟可以选择一个整数 i i i ,使得 1 ≤ i ≤ n 1 \le i \le n 1≤i≤n ,并将 x x x 设为 mex † ( x , a i , 1 , a i , 2 , … , a i , l i ) \text{mex}^{\dagger}(x, a_{i, 1}, a_{i, 2}, \ldots, a_{i, l_i}) mex†(x,ai,1,ai,2,…,ai,li) 。
- 乌龟被要求找出答案,即进行任意次数运算后 x x x 的最大值。
乌龟毫不费力地解决了上述问题。他将 f ( k ) f(k) f(k) 定义为当 x x x 的初始值为 k k k 时上述问题的答案。
然后,小猪给了乌龟一个非负整数 m m m ,让乌龟找出 ∑ i = 0 m f ( i ) \sum\limits_{i=0}^m f(i) i=0∑mf(i) 的值(即 f ( 0 ) + f ( 1 ) + … + f ( m ) f(0) + f(1) + \ldots + f(m) f(0)+f(1)+…+f(m) 的值)。不幸的是,他无法解决这个问题。请帮帮他!
† mex ( c 1 , c 2 , … , c k ) ^{\dagger}\text{mex}(c_1, c_2, \ldots, c_k) †mex(c1,c2,…,ck) 被定义为在序列 c c c 中不出现的最小非负整数 x x x 。例如, mex ( 2 , 2 , 0 , 3 ) \text{mex}(2, 2, 0, 3) mex(2,2,0,3) 是 1 1 1 , mex ( 1 , 2 ) \text{mex}(1, 2) mex(1,2) 是 0 0 0 。
思路:
不难发现一个性质,假设一个序列最小未出现的非负整数为 m e x 1 mex1 mex1,第二小未出现的非负整数为 m e x 2 mex2 mex2,那么只要这个数不是 m e x 1 mex1 mex1,做一次运算的结果就是 m e x 1 mex1 mex1,做两次运算的结果就是 m e x 2 mex2 mex2。每个序列最大能 m e x mex mex 到的只能是到 m e x 2 mex2 mex2。
所以我们只需要找到所有序列最大的 m e x 2 mex2 mex2,让小于 m e x 2 mex2 mex2 的数都变成 m e x 2 mex2 mex2,这样 f ( 0 ) + f ( 1 ) + … + f ( m ) = m e x 2 + m e x 2 + ⋯ + m e x 2 + ( m e x 2 + 1 ) + ( m e x 2 + 2 ) + ⋯ + m = ( m e x 2 + 1 ) ∗ m e x 2 + ( m + m e x 2 + 1 ) ∗ ( m − m e x 2 ) / 2 f(0) + f(1) + \ldots + f(m)=mex2+mex2+\dots+mex2+(mex2+1)+(mex2+2)+\dots+m=(mex2+1)*mex2+(m+mex2+1)*(m-mex2)/2 f(0)+f(1)+…+f(m)=mex2+mex2+⋯+mex2+(mex2+1)+(mex2+2)+⋯+m=(mex2+1)∗mex2+(m+mex2+1)∗(m−mex2)/2 就是最大的。
code:
#include <iostream>
#include <cstdio>
#include <set>
using namespace std;
typedef long long ll;
int T,n,m,mx;
int main(){
cin>>T;
while(T--){
cin>>n>>m;
mx=0;
for(int i=1,l;i<=n;i++){
cin>>l;
set<int> S;
bool f=false;
for(int j=1,t;j<=l;j++){
cin>>t;
S.insert(t);
}
for(int j=0;;j++){
if(S.find(j)==S.end())
if(!f)f=true;
else {
mx=max(mx,j);
break;
}
}
}
ll ans=0;
if(mx<m)ans=1ll*(mx+1)*mx+1ll*(m-mx)*(m+mx+1)/2;
else ans=1ll*mx*(m+1);
cout<<ans<<endl;
}
return 0;
}
D2. Turtle and a MEX Problem (Hard Version)
题面:
两个版本是不同的问题。在这个版本的问题中,你不能两次或多次选择同一个整数。只有两个版本的问题都解决了,你才能进行破解。
有一天,乌龟在玩 n n n 序列。假设第 i i i 个序列的长度为 l i l_i li 。那么第 i i i 个序列是 a i , 1 , a i , 2 , … , a i , l i a_{i, 1}, a_{i, 2}, \ldots, a_{i, l_i} ai,1,ai,2,…,ai,li .
小猪在乌龟玩耍的时候给乌龟出了一道难题。问题的陈述是
- 一开始有一个非负整数 x x x 。小乌龟要对这个整数进行任意次数(可能是零)的运算。
- 在每一次运算中,乌龟可以选择 i i i 这样一个整数,即 1 ≤ i ≤ n 1 \le i \le n 1≤i≤n 和 i i i 之前没有被选择,并将 x x x 设置为 mex † ( x , a i , 1 , a i , 2 , … , a i , l i ) \text{mex}^{\dagger}(x, a_{i, 1}, a_{i, 2}, \ldots, a_{i, l_i}) mex†(x,ai,1,ai,2,…,ai,li) 。
- 乌龟被要求找出答案,即进行任意次数运算后 x x x 的最大值。
乌龟毫不费力地解决了上述问题。他将 f ( k ) f(k) f(k) 定义为当 x x x 的初始值为 k k k 时上述问题的答案。
然后,小猪给了乌龟一个非负整数 m m m ,让乌龟找出 ∑ i = 0 m f ( i ) \sum\limits_{i=0}^m f(i) i=0∑mf(i) 的值(即 f ( 0 ) + f ( 1 ) + … + f ( m ) f(0) + f(1) + \ldots + f(m) f(0)+f(1)+…+f(m) 的值)。不幸的是,他无法解决这个问题。请帮帮他!
† mex ( c 1 , c 2 , … , c k ) ^{\dagger}\text{mex}(c_1, c_2, \ldots, c_k) †mex(c1,c2,…,ck) 定义为序列 c c c 中不出现的最小非负整数 x x x 。例如, mex ( 2 , 2 , 0 , 3 ) \text{mex}(2, 2, 0, 3) mex(2,2,0,3) 是 1 1 1 , mex ( 1 , 2 ) \text{mex}(1, 2) mex(1,2) 是 0 0 0 。
思路:
其实不难,但是赛时被前三个题整麻了,就不觉得这个能做上来,结果这个题就赛时坠机了,差一点写出来。
这个题和上一个的区别在于不能对一个序列进行多次 m e x mex mex 运算,只能做一次。还是假设一个序列最小未出现的非负整数为 m e x 1 mex1 mex1,第二小未出现的非负整数为 m e x 2 mex2 mex2。所以对一个序列,我们做一次 m e x mex mex,只要这个数不是一开始就是 m e x 1 mex1 mex1,就会得到 m e x 1 mex1 mex1。
但是如果这个数一开始就是 m e x 1 mex1 mex1,这时可以得到 m e x 2 mex2 mex2。或者是有另一个序列的最小未出现的非负整数也是这个 m e x 1 mex1 mex1,那么无论什么数都可以得到 m e x 2 mex2 mex2。其实可以发现一个数变成另一个数的方法就这两种。
我们用一条有向边 m e x 1 → m e x 2 mex1\rightarrow mex2 mex1→mex2 表示原本为 m e x 1 mex1 mex1 的数可以直接得到 m e x 2 mex2 mex2。再用一个 m x mx mx 表示所有数都可以变成的最大的 m e x 2 mex2 mex2。那么对一个数,要么不变,要么变成有向边连向的数,要么变成 m x mx mx。
可以发现 m e x 2 mex2 mex2 的大小是随着序列长度变化的,它最大就是 l + 1 l+1 l+1,而 ∑ l ≤ 2 × 1 0 5 \sum l\le 2\times 10^5 ∑l≤2×105,所以 m e x 1 → m e x 2 mex1\rightarrow mex2 mex1→mex2 以及 m e x 2 mex2 mex2 最大也就到 2 × 1 0 5 2\times 10^5 2×105。我们可以用 t o to to 数组表示每个数可以变成的最大的数, t o to to 数组有可能发生变化的也只有前 m e x 2 mex2 mex2 的部分。我们处理这部分的 t o to to 数组不会超时。高出的部分直接等差数列求和即可。
code:
#include <iostream>
#include <cstdio>
#include <set>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int maxn=2e5+5;
typedef pair<int,int> pii;
int T,n,m;
int top;
pii ttt[maxn];
int M;
vector<int> head,d,to;
int ent;
struct edge{
int v,nxt;
}e[maxn];
void add(int u,int v){
e[++ent]={v,head[u]};
head[u]=ent;
}
signed main(){
cin>>T;
while(T--){
cin>>n>>m;
ent=M=top=0;
for(int i=1,l;i<=n;i++){
cin>>l;
set<int> S;
int k1=-1,k2;
for(int i=1,t;i<=l;i++){
cin>>t;
S.insert(t);
}
for(int j=0;;j++){
if(S.find(j)==S.end()){
if(!~k1)k1=j;
else {
k2=j;
break;
}
}
}
M=max(M,k2);
ttt[++top]=pii(k1,k2);
}
head.resize(M+1);
d.resize(M+1);
to.resize(M+1);
for(int i=0;i<=M;i++)head[i]=d[i]=to[i]=0;
for(int i=1;i<=top;i++){
int k1=ttt[i].first,k2=ttt[i].second;
d[k1]++;
add(k1,k2);
}
for(int u=M;u>=0;u--){
to[u]=u;
for(int i=head[u],v;i;i=e[i].nxt){
v=e[i].v;
to[u]=max(to[u],to[v]);
}
}
int mx=0;
for(int i=0;i<=M;i++)
if(d[i]>1)mx=max(mx,to[i]);
else if(d[i]==1)mx=max(mx,i);
ll ans=0;
for(int i=0;i<=min(M,m);i++){
ans+=max(to[i],mx);
}
if(m>M)ans+=1ll*(m+M+1)*(m-M)/2;
cout<<ans<<endl;
}
return 0;
}