CF1530E Minimax
\(CF1530E\ Minimax\)
题意
给一个字符串 \(s\),要求将 \(s\) 重新排列得到 \(t\),使得:
-
对于 \(t\) 的每个前缀 \(i\),定义 \(f(i)\) 为前缀 \(i\) 的 \(border\),使得所有位置的 \(f(i)\) 最大值最小
-
满足条件的 \(t\) 的字典序最小
思路分析
思路其实很简单清晰,即:
和
然后瞎推就完了
首先不难发现一个结论:
对于任意串 \(s\),当 \(s\) 中存在至少2种字符时,必然存在 \(t\) 使得 \(f(t)=0\) 或 \(1\).
证明将在下文中体现。
然后约定几个变量:
- \(st\) 为 \(s\) 中出现的字典序最小的字符。
- \(se\) 为 \(s\) 中出现的字典序第二小的字符(如果存在至少两种字符)。
- \(th\) 为 \(s\) 中出现的字典序第三小的字符(如果存在至少三种字符)。
- \(cnt[c]\) 为 \(s\) 中字符 \(c\) 出现的次数。
- \(kinds\) 为 \(s\) 中字符种数。
for(int i='a';i<='z';i++) cnt[i]=0; st='z'+1;int kinds=0; scanf(" %s",s+1); n=strlen(s+1); for(int i=1;i<=n;i++){ if(!cnt[s[i]]) kinds++; cnt[s[i]]++; st=min(st,s[i]); }
那么,开始酣畅淋漓的分讨特判吧~
壹.特判 \(f(t)=0\).
不难发现,当且仅当 \(\exists \ i,cnt[i]=1\) 时,能够满足 \(f(i)=0\).比如串\(s=\)"\(bababc\)",\(t\) 显然为 "caabbb".
code
bool b=0; for(int i='a';i<='z';i++){ if(cnt[i]==1){ pc(i);--cnt[i]; for(int j='a';j<='z';j++) while(cnt[j]-->0) pc(j); b=1;pt;break; } } if(b) continue;
贰.特判 \(kinds=1\)
直接输出即可.
if(kinds==1){ for(int i=1;i<=n;i++) pc(st); pt;continue; }
叁.对于 \(f(t)=1\) 进行分讨
1. \(kinds=2\)
为了让字典序最小,显然要先尽可能多地输出 \(st\) (最多显然是\(2\)个),那么为了让 \(f(t)=1\),接着分讨。
-
\(cnt[st]=2\) 时,例如"\(abbabbb\)",直接按字典序输出即可,即"\(aabbbbb\)"
-
\(cnt[st]=3\) 时,例如"\(abbaabb\)",考虑先输出 \(2\) 个 \(st\),再用 \(se\) 分割一下,输出剩下的一个 \(st\),再输出剩下的 \(se\),即"\(aababbb\)"
-
\(cnt[st]>3\) 时,我们分讨要先输出几个 \(st\).
-
\(cnt[st]-1>cnt[se]+1\) 时,例如"\(aabbaaaa\)"不难发现此时 \(st\) 一定会连着出现至少两次,(考虑用 \(se\) 分割),如果用两个 \(st\) 开头,必然会令 \(f(t)=2\),(比如"\(aababaaa\)")。所以必然要以一个 \(st\) 开头。然后输出所有 \(se\),(至于为什么输出所有的,显然 \(f(abaaaabb)=2\)),最后输出剩下的 \(st\),即"\(abbaaaaa\)"
-
\(cnt[st]-1 \leq cnt[se]+1\) 时,例如"\(aabaabbbb\)"不难发现此时 \(st\) 可以被 \(se\) 分隔开使得没有 \(st\) 相邻,那么为了让字典序最小,我们用两个 \(st\) 开头,接着循环输出"\(se\ st\ se\ st \dots\)"即可。另外,由于 \(cnt[st]-2 < cnt[se]\) 所以剩下的一定是 \(se\)。输出即可。即"\(aabababbb\)".
-
code
for(se=st+1;se<='z';se++) if(cnt[se]) break; if(kinds==2){ if(cnt[st]==2){ pc(st),pc(st),cnt[st]-=2; while(cnt[se]-->0) pc(se); } else if(cnt[st]==3){ pc(st),pc(st),cnt[st]-=2; pc(se),--cnt[se]; pc(st),--cnt[st]; while(cnt[se]-->0) pc(se); } else{ if(cnt[st]-1>cnt[se]+1){ pc(st);cnt[st]--; while(cnt[se]-->0) pc(se); while(cnt[st]-->0) pc(st); } else{ pc(st),pc(st),cnt[st]-=2; while(cnt[st] && cnt[se]){ pc(se),pc(st); --cnt[st];--cnt[se]; } while(cnt[se]-->0) pc(se); } } pt;continue; }
2. \(kinds \geq 3\)
-
\(cnt[st]==2\) 以及 \(cnt[st]==3\) 时,方法同上。
-
\(cnt[st]>3\) 时,与上文类似的,令 \(els=n-cnt[st]\).讨论:
-
\(cnt[st]-1>els+1\) 时,例如"\(aabbaaacaac\)",上文已证,应使用一个 \(st\) 开头,然后用一个 \(se\) 分隔,接着输出所有 \(st\) ,为了不重复出现"\(st\ se\)",我们再用 \(th\) 分隔,然后以字典序输出剩下所有字符,即"\(abaaaaaacbc\)".
-
\(cnt[st]-1\leq els+1\) 时,例如"\(aababcabc\)",上文已证,应使用两个 \(st\) 开头,然后用剩下的其他字符将剩下的 \(st\) 分隔输出,输出完 \(st\) 后按字典序输出剩下的其他字符即可。即"\(aabababcc\)".
-
code
if(kinds>=3){ if(cnt[st]==2){ pc(st),pc(st),cnt[st]=0; for(int i=st+1;i<='z';i++) if(cnt[i]) while(cnt[i]-->0) pc(i); } else if(cnt[st]==3){ pc(st),pc(st); pc(se),--cnt[se]; pc(st);cnt[st]=0; for(int i=st+1;i<='z';i++) if(cnt[i]) while(cnt[i]-->0) pc(i); } else{ int els=n-cnt[st]; if(cnt[st]-1>els+1){ pc(st);--cnt[st]; pc(se);--cnt[se]; while(cnt[st]-->0) pc(st); pc(th);--cnt[th]; for(int i=st+1;i<='z';i++) if(cnt[i]) while(cnt[i]-->0) pc(i); } else{ pc(st),pc(st),cnt[st]-=2; for(int i=st+1;i<='z';i++){ while(cnt[i] && cnt[st]) pc(i),pc(st),--cnt[i],--cnt[st]; if(!cnt[st]) while(cnt[i]-->0) pc(i); } } } pt; }
真是一场酣畅淋漓的分讨啊
\(AC\ \ code\)
#include<bits/stdc++.h> using namespace std; #define pc putchar #define read read() #define pt puts("") inline int read { int x=0,f=1;char c=getchar(); while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();} while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar(); return f*x; } void write(int x) { if(x<0) pc('-'),x=-x; if(x>9) write(x/10); pc(x%10+'0'); return; } #define N 100010 int q,n; char s[N]; int cnt[130]; char st='z'+1,se,th; signed main() { #ifndef ONLINE_JUDGE freopen("lty.in","r",stdin); freopen("lty.out","w",stdout); #endif q=read; while(q-->0){ for(int i='a';i<='z';i++) cnt[i]=0; st='z'+1;int kinds=0; scanf(" %s",s+1); n=strlen(s+1); for(int i=1;i<=n;i++){ if(!cnt[s[i]]) kinds++; cnt[s[i]]++; st=min(st,s[i]); } bool b=0; for(int i='a';i<='z';i++){ if(cnt[i]==1){ pc(i);--cnt[i]; for(int j='a';j<='z';j++) while(cnt[j]-->0) pc(j); b=1;pt;break; } } if(b) continue; if(kinds==1){ for(int i=1;i<=n;i++) pc(st); pt;continue; } for(se=st+1;se<='z';se++) if(cnt[se]) break; if(kinds==2){ if(cnt[st]==2){ pc(st),pc(st),cnt[st]-=2; while(cnt[se]-->0) pc(se); } else if(cnt[st]==3){ pc(st),pc(st),cnt[st]-=2; pc(se),--cnt[se]; pc(st),--cnt[st]; while(cnt[se]-->0) pc(se); } else{ if(cnt[st]-1>cnt[se]+1){ pc(st);cnt[st]--; while(cnt[se]-->0) pc(se); while(cnt[st]-->0) pc(st); } else{ pc(st),pc(st),cnt[st]-=2; while(cnt[st] && cnt[se]){ pc(se),pc(st); --cnt[st];--cnt[se]; } while(cnt[se]-->0) pc(se); } } pt;continue; } for(th=se+1;th<='z';th++) if(cnt[th]) break; if(kinds>=3){ if(cnt[st]==2){ pc(st),pc(st),cnt[st]=0; for(int i=st+1;i<='z';i++) if(cnt[i]) while(cnt[i]-->0) pc(i); } else if(cnt[st]==3){ pc(st),pc(st); pc(se),--cnt[se]; pc(st);cnt[st]=0; for(int i=st+1;i<='z';i++) if(cnt[i]) while(cnt[i]-->0) pc(i); } else{ int els=n-cnt[st]; if(cnt[st]-1>els+1){ pc(st);--cnt[st]; pc(se);--cnt[se]; while(cnt[st]-->0) pc(st); pc(th);--cnt[th]; for(int i=st+1;i<='z';i++) if(cnt[i]) while(cnt[i]-->0) pc(i); } else{ pc(st),pc(st),cnt[st]-=2; for(int i=st+1;i<='z';i++){ while(cnt[i] && cnt[st]) pc(i),pc(st),--cnt[i],--cnt[st]; if(!cnt[st]) while(cnt[i]-->0) pc(i); } } } pt; } } return 0; }
\(CF\)*\(2100\) 难度,比起题单上其他题,真是挺水。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下