CF1905C Largest Subsequence 题解
首先考虑如何生成一个字典序最大的子序列。我们先考虑找到字符串中的最大元素,然后在其之后找到第二大的元素,重复这个过程,直到达到序列末尾。
我们可以通过记录每种字母的出现位置,从大往小枚举。每碰到一个字母在当前位置之后,将这个字母加入序列,并将当前位置修改为这个位置。
容易发现,最后的操作一定是对于初始字符串中字典序最大的子序列进行多次移位操作。
因为一次移位之后,最大字符只是被移向该子序列后一位,除去被移走的元素,下一次操作的字符集没有改变,一定是初始字符串中字典序最大的子序列的子序列。而根据生成方式,被移走的元素字典序靠后,故字典序偏小,被移到前面没有坏处,故不需要第二次移动。所以,我们可以看做每次操作最初的序列,已经移到开头的较小元素不再被移动。
因此,我们只考虑操作最初的子序列。根据生成方式,在这个子序列中,较小的元素靠后,所以为了消除所有逆序对,我们需要把最大的元素移动的最后。
注意并不一定是要把第一个元素移动到最后,因为可能会存在多个相同的元素,只要其中一个移动到序列末尾即可,再移动没有意义。
根据上述移动方式,序列中最小的元素一定移动到开头,最大的元素一定移动到末尾。我们直接记录这个序列,然后翻转即可。
操作结束后,直接判定是否存在逆序对即可。顺序遍历序列,记录遍历过的最大值,如果现在的元素小于最大值,证明存在逆序对,无解。
总体时间复杂度 。
#include <bits/stdc++.h>
using namespace std;
long long t,n,cnt,now,cw[300000],cv[300000];
char s[300000];
vector<long long>w[30];
int main()
{
scanf("%lld",&t);
while(t--)
{
long long ans=0;
cnt=0,now=0;
scanf("%lld",&n);
scanf("%s",s+1);
for(int i=1;i<=26;i++)w[i].clear();
for(int i=1;i<=n;i++)w[s[i]-'a'+1].push_back(i);
for(int i=26;i>=1;i--)
{
long long l=w[i].size();
for(int j=0;j<l;j++)
if(w[i][j]>now)now=w[i][j],cw[++cnt]=w[i][j],cv[cnt]=i;
}
ans=cnt-1;
while(cv[cnt-ans]==cv[1]&&ans>=0)ans--;
ans++;
for(int i=1;i<=cnt;i++)s[cw[i]]='a'+cv[cnt-i+1]-1;
now=0;
for(int i=1;i<=n;i++)
if(s[i]-'a'+1>=now)now=s[i]-'a'+1;
else
{
ans=-1;
break;
}
printf("%lld\n",ans);
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探