题解 CF1621I 【Two Sequences】
fz 汉化组。
做法和证明基本上来源于官方题解和 ecnerwala 题解。
生肉太难啃了。(哭)
首先改写 op 操作的每一步。在第
形式化一下,设
接着我们断言经过
考虑序列的最长非升前缀
那么,
这也就是说,一次 op 要么使序列变得非升,要么使这段连续相等字符的长度平方,那么有效的 op 操作最多进行
令
从前往后考虑
至此,我们其实已经能够得到一些做法,比如官方题解给出的
寻找更强的结论,我们继续断言:若
记
首先注意到
注意到
接下来,剩下的问题就只有如何对一个串的每个前缀找出其最小后缀和这个后缀的最大循环次数了。
题外话,其实我们只需要找到最小后缀,最大循环次数可以暴力找,反正我们只需要知道结果的前
首先我们知道,最小后缀就是 lyndon 分解的最后一段,最大循环次数就是末尾相等的段数。这两个结论的证明留给读者,或可以自行查阅 lyndon 理论相关内容。
回忆 lyndon 分解的 Duval 算法。我们维护了当前前缀的一个后缀,这里暂称之为“未定串”,形如
对于当前字符小于
现在我们知道了算法从头开始执行完每个位置(即,完全不考虑之后的位置)时的未定串。若其
至此,我们完整解决了这个问题。单次 op 操作时空复杂度
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
inline ll readint(){
ll x=0;
bool f=0;
char c=getchar();
while(!isdigit(c)&&c!='-') c=getchar();
if(c=='-'){
f=1;
c=getchar();
}
while(isdigit(c)){
x=x*10+c-'0';
c=getchar();
}
return f?-x:x;
}
const int maxn=1e5+5;
int n,q,b[7][maxn];
int s[maxn],s2[maxn],tp,l[maxn],l2[maxn];
void op(int* c,int* d){
tp=0;
for(int i=1;i<=n;i++){
while(tp&&c[i]<s[tp-s2[tp]+1]) tp%=s2[tp];
if(!tp||c[i]>s[tp-s2[tp]+1]) s2[tp+1]=tp+1;
else s2[tp+1]=s2[tp];
s[++tp]=c[i];
if(tp%s2[tp]==0){
l[i]=i-s2[tp]+1;
l2[i]=i-tp+1;
}
else{
l[i]=l[i-s2[tp]]+s2[tp];
l2[i]=l2[i-s2[tp]]+s2[tp];
}
}
for(int i=n;i>0;i--)
if(i==n||l2[i+1]==i+1) l2[i]=l[i];
int cnt=0;
for(int i=1;i<=n&&cnt<n;i++)
for(int j=l2[i];j<=i&&cnt<n;j++) d[++cnt]=c[j];
}
int main(){
#ifdef LOCAL
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
n=readint();
for(int i=1;i<=n;i++) b[0][i]=readint();
for(int i=1;i<=6;i++) op(b[i-1],b[i]);
q=readint();
while(q--){
int i,j;
i=min(readint(),6ll);
j=readint();
printf("%d\n",b[i][j]);
}
#ifdef LOCAL
fprintf(stderr,"%f\n",1.0*clock()/CLOCKS_PER_SEC);
#endif
return 0;
}