题解:CF1886C Decreasing String
前置知识:链表
首先,每次删除的一定是比后一个字符大且位置最靠前的字符,例如 cadb 下一次删除的是第一个 c。
证明:
- 如果当前字符比后面字符大,删掉这个字符并让后面向前移动一位一定会使字典序变小(这个位置被更小的后一个字符替代);
- 如果有多个字符满足上述条件,那么删掉最前面的一个可以使字典序减小的位置最靠前(比如
cadb删c是在第一个位置减小字典序,删d是在第三个位置减小字典序),而根据字典序的定义,小字符越靠前,字典序越小。
综上所述,每次删除第一个比后一个字符大的字符是最优的。
有了这个结论,我们就可以求字符串中各个字符被删除的顺序了,但是直接求是
观察下面的删字符过程:
abandon
a_andon
a_a_don
a_a_d_n
a_a_d__
a_a____
a______
可以发现,删字符的过程是从前往后删掉所有的逆序,字符串变成不下降以后再从后往前依次删除。
其实把
再来看一个例子:
acdcbb
ac_cbb
ac__bb
a___bb
a___b_
a_____
这个就稍微有所不同了,在第
但是,有一个结论:当删除字符
所以,我们在每次删除成功后将位置向前移动一位(只有这一位可能回退形成新的逆序),尝试删除,这样就可以解决删除顺序回退的问题了。
同时,最后从后往前删的问题也能通过这种方式解决,删完最后一个以后回退一位(该字符是末尾字符,所以此时再尝试删除一定成功)即可做到从后往前删除。
然而,直接向前找没被删掉的字符,时间复杂度是
回想一下,我们要实现快速删除元素和找前驱后继的操作,而这正是链表干的事情(终于有一道链表题了)。
(不过这个似乎用栈也可以维护,但是用链表要更加直观一些。)
通过上述过程,我们
接下来,直接
设
AC 记录
完整代码:
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1e6+5;
int n; long long pos;
char s[N];
int order[N],oidx;
struct BJ{
int pre,nxt;
}ls[N];
inline void del(int x)
{
ls[ls[x].pre].nxt=ls[x].nxt;
ls[ls[x].nxt].pre=ls[x].pre;
return;
}
int main()
{
int T; scanf("%d",&T);
while(T--)
{
scanf("%s%lld",s+1,&pos);
n=strlen(s+1);
for(int i=1;i<=n;i++)
ls[i]={i-1,i+1};
oidx=0;
for(int i=1;i<=n;)
{
if(s[i]>s[ls[i].nxt])
{
order[i]=++oidx;
del(i);
i=ls[i].pre;
}
else i=ls[i].nxt;
}
int part=0; long long tot=0;
for(int i=0;i<=n;i++)
{
tot+=n-i;
if(tot>=pos)
{
part=i;
break;
}
}
int real_pos=pos-1ll*(n*2-(part-1))*((part-1)+1)/2;
int sidx=0;
char ans='-';
for(int i=1;i<=n;i++)
if(order[i]>part)
{
sidx++;
if(sidx==real_pos)
{
ans=s[i];
break;
}
}
putchar(ans);
}
return 0;
}