简单说一下这场吧,赛时写D题时犯大病了,赛后才发现求线段最大间隔就行了,E题最后改完bug的时候差几秒钟交上去,赛后交了一发过了,又错失了一次上大分的机会qwq。
A. Milica and String 模拟即可。
ll n,k;
inline void qfl_zzz(){
n=read(),k=read();
string s=sread();
ll cnt=0;
for(ll i=1;i<=n;++i)
cnt+=(s[i]=='B');
if(cnt==k){
writen(0);
return;
}
if(cnt>k){
cnt-=k;
for(ll i=1;i<=n;++i){
cnt-=(s[i]=='B');
if(cnt==0){
writen(1),writet(i),printf("A\n");
return;
}
}
}
else{
cnt=k-cnt;
for(ll i=1;i<=n;++i){
cnt-=(s[i]=='A');
if(cnt==0){
writen(1),writet(i),printf("B\n");
return;
}
}
}
}
B. Milena and Admirer
注意到从 中拆出来的
是被放在前面的,所以如果我们从前往后操作的话,可能会破坏前面单调不降的限制,于是考虑从后往前考虑操作;
对于
,我们假设他后面的数字是
,那么我们可以求出把
的每一份都尽可能分成
最多可以分成多少份,显然是
,每一份不超过
保证了单调不降的限制;
一个数字被分成了
份显然是进行了
次操作,然后我们把
更新成
即可。
ll n,a[200005];
inline void qfl_zzz(){
n=read();
for(ll i=1;i<=n;++i)a[i]=read();
ll ans=0,pre=1e18;
for(ll i=n;i>=1;--i){
if(a[i]<=pre){
pre=a[i];
continue;
}
ll cnt=(a[i]+pre-1)/pre;
ans+=cnt-1,pre=a[i]/cnt;
}
writen(ans);
}
C. Colorful Grid
首先我们可以很容易地知道从起点到终点的曼哈顿距离 ,而对于
的情况,我们需要通过在图上 “绕圈” 或者 “多花两步拐一个弯代替一次直走” 来抵消掉多余的路程;
不难发现,绕圈每一次花费 4 步路程,绕弯代替直走则多花费 2 步路程,因此,我们可以通过这两个操作抵消掉偶数长度的额外路程,所以当
是奇数时,无解;
显然,我们可以指定一个角落进行绕圈操作,指定另外一个角落进行绕弯操作,这两个角落可以直接选在起点和终点的位置以便输出答案,具体实现方式有很多,笔者给出其中比较拙劣的一种。
ll n,m,k,r[20][20],c[20][20];
inline void qfl_zzz(){
memset(r,-1,sizeof(r));
memset(c,-1,sizeof(c));
n=read(),m=read(),k=read();
ll x=k-n-m+2;
if(x<0||x%2==1){
printf("NO\n");
return;
}
printf("YES\n");
r[1][1]=r[2][1]=1;
c[1][1]=c[2][1]=0;
if((n+m)%2)r[n][m-1]=c[m-1][n-1]=c[m][n-1]=0,r[n-1][m-1]=1;
else r[n][m-1]=c[m-1][n-1]=c[m][n-1]=1,r[n-1][m-1]=0;
for(ll i=m-2;i>=1;--i)r[n][i]=1-r[n][i+1];
for(ll i=2;i<=n-1;++i)c[1][i]=1-c[1][i-1];
for(ll i=1;i<=n;++i,printf("\n"))
for(ll j=1;j<=m-1;++j){
ll x=r[i][j];
if(x==-1||x==1)printf("R ");
else printf("B ");
}
for(ll i=1;i<=n-1;++i,printf("\n"))
for(ll j=1;j<=m;++j){
ll x=c[j][i];
if(x==-1||x==1)printf("R ");
else printf("B ");
}
}
D. Absolute Beauty
将 和
当作是线段的两个端点,那么交换
的值相当于是交换了两个线段的端点,稍加模拟一下就会发现,交换之后多出了两倍的原线段之间的间隔,于是求最大两线段的间隔即可。
ll n,a[200005],b[200005];
inline void qfl_zzz(){
n=read();
ll ans=0,r=1e9,l=0;
for(ll i=1;i<=n;++i)a[i]=read();
for(ll i=1;i<=n;++i)b[i]=read();
for(ll i=1;i<=n;++i){
l=max(l,min(a[i],b[i]));
r=min(r,max(a[i],b[i]));
ans+=abs(a[i]-b[i]);
}
writen(ans+max(0ll,2*(l-r)));
}
E. Sofia and Strings
如果暴力地去模拟操作,那么显然复杂度是难以接受的;
于是考虑将操作作为微调的手段,即我们无需去实际进行这些操作,而是当我们需要达到一些目的的时候,思考一下这两个操作能够为我们达到目的提供什么帮助;
对于此题,我们可以从左到右依次考虑 来自于
中的哪一个字符;
对于每一个
,我们应该选取
中当前最早出现
的位置来进行配对;
如何理解 “当前” ?比如在
之前,我们已经在
中配对了一些与
相同的字符,那么这些字符在
中对应的位置就不能再用于与当前的
的配对了;
如何理解 “最早” ?显然,在
后面的可能会存在与
相同的字符,他们也需要用与
同样的字符来与
中对应的位置来配对,如果不使用最早的,那么就会造成浪费,导致后面的有些字符无法配对;
我们假设
为
中需要与
进行配对的位置,那么显然在
之前的位置中,不能存在比
更小的字符;
为什么?因为位置
可能不是与
所在的位置对齐的,所以这时我们需要通过操作2:排序来将
对齐到相应位置,如果此时在位置
之前有存在更小的字符,设它的位置为
,那么与
对齐的就是
而不是
了,因为排序后位置
对应的字符显然会排在更前面;
那么如果出现了这种
之前存在更小字符的情况该怎么办呢?不要忘了我们还有操作1:删除,显然我们可以通过操作 1 将这些在
之前的更小字符给逐个删去;
具体实现看代码。
ll n,m;
inline void qfl_zzz(){
n=read(),m=read();
string s=sread(),t=sread();
stack<ll> id[26];
for(ll i=n;i>=1;--i)id[s[i]-'a'].push(i);
for(ll i=1;i<=m;++i){
ll e=t[i]-'a';
if(id[e].size()==0){
printf("NO\n");
return;
}
for(ll j=0;j<e;++j)
while(id[j].size()>0&&id[j].top()<id[e].top())
id[j].pop();
id[e].pop();
}
printf("YES\n");
}