做题记录 2026.03~2026.06
2026.03
*ARC133F Random Transition
看这种对位乘下标,想到生成函数的 \(\vartheta\) 算子,即 \(x\frac{d}{dx}\),效果是 \(\vartheta x^i=ix^i\)。
设 \(k\) 次操作后概率函数为 \(F_k(z)\),则
化简得
根据 GeMini,考虑这个微分算子的特征多项式。
接下来参考本文。
设特征值分解的系数是 \(q\),其生成函数是 \(Q\),则
换元,设 \(u=\frac{1+z}{1-z}\) 可得
然后可以直接用这个方法,当然也有笔者口胡的更简单做法:
分离常数 \(F_0\left(\frac{u-1}{u+1}\right)=F_0\left(1-\frac{2}{1+u}\right)\)。
多项式平移求出 \(F_0(1+x)\);再奇数位取反求出 \(F_0(1-x)\);再逐位乘 \(2\) 幂求出 \(F_0(1-2x)\);再把系数 reverse 求出 \(x^nF_0(1-\frac{2}{x})\);再平移求出 \((1+x)^nF_0(1-\frac{2}{1+x})\);最后多项式求逆除掉 \((1+x)^n\),求出 \(F_0\left(1-\frac{2}{1+x}\right)\)。
*AT_abc238_h [ABC238Ex] Removing People
逆序考虑
显然要把期望转为求和。从只剩一个人开始,每次加一个人,会把序列划分为左右两段,于是区间 DP。
维护区间内的方案数与所有方案代价之和,转移合并两个区间时需要乘上两个排列交错的组合数。
有细节。
2026.05
*tsinsen Djq9l. inv(八校春季训练 0208)
tj。
2026.06
AT_agc022_e [AGC022E] Median Replace
考虑判定问题。
有两种判定方法,主流的是本文,读者自行跳转查看,下面介绍另类方法。
注意到这题的操作和下一题 P9151 计数题 一样,用下一题的判定方法。把操作看成 删 字符(删连续的 \(01/10\),以及删 \([00]0/0[00]/[11]1/1[11]\))。让最后留下的 \(1\) 尽可能靠前。注意到只要 \([1,i-1]\) 能删空,且 \(S_i=1\),且最终可以操作为 \(1\),那么可以操作使得留下的 \(1\) 是 \(i\)。
???+ 证明
设可以让 \(j\) 上的 \(1\) 留下(一定存在,因为可以操作为 \(1\)),则已知 \([1,i-1],[j+1,n]\) 可删空。
只需证明 \([i+1,j-1]\)(或 \([j+1,i-1]\),取决于大小关系,不妨设 \(i<j\))可删空。
因为可删空的区间长度一定是偶数,所以 \(i,j\) 为奇数,所以 \(i,j\) 间有奇数个位置,胡乱操作,让中间这部分只剩下一个数,由于 \(S_i=S_j=1\),一定占据中位数位置,所以 \([i+1,j-1]\) 可删空。
???-
运用下一题对首段、尾段的判定,可写出总的判定过程:
- 找最后留下的 \(1\) 的位置 \(i\):若 \(S_1=1\),则 \(i=1\);否则从 \(p=1\) 开始,每次跳两个字符 \(p+1,p+2\),看这两个字符是不是 \(11\),若是则 \(i=p+2\)。
- 从 \(p=i\) 开始往后跳,每次跳两个字符 \(p+1,p+2\),看是否满足 \(S_p=S_{p+1}=1\) 若是,则 \(i\) 合法;否则,若跳到 \(p=n\) 后满足 \(S_p=1\),也合法。
写出自动机:

当然这个做法每次转移两个字符,不好写就是了,代码写的是主流设计。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=1000000007;
void add(int &x,int y){
x+=y;
if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
x+=y;
if(x>=MOD) x-=MOD;
return x;
}
const int N=300005;
char s[N];
int n,f[N][10];
int trans[10][2]={
{3,1},{4,2},{5,9},
{6,0},{7,1},{8,2},
{3,3},{4,4},{5,5},
{9,9}
};
int main(){
scanf("%s",s+1);
n=strlen(s+1);
f[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<10;j++){
for(int c=0;c<2;c++) if(s[i]=='?'||s[i]=='0'+c) add(f[i][trans[j][c]],f[i-1][j]);
}
}
printf("%d\n",add2(f[n][1],add2(f[n][5],f[n][9])));
return 0;
}
P9151 计数题
思维链:
- 转化操作;
- 注意到保留下来的一定是子序列,故考虑类似子序列自动机状物,需要仔细证明一下取最靠前的一定合法;
- 考虑如何构造子序列自动机;
- 特殊处理首段、尾段,写出 DP。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
void add(int &x,int y){
x+=y;
if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
x+=y;
if(x>=MOD) x-=MOD;
return x;
}
int qpow(int a,ll b){
int mul=1;
while(b){
if(b&1) mul=(ll)mul*a%MOD;
a=(ll)a*a%MOD;
b>>=1;
}
return mul;
}
const int N=5000005;
int n,trans[N][2],f[N];
char s[N];
void __INIT__(){}
void __SOLVE__(){
scanf("%s",s+1);
n=strlen(s+1);
int lst1[2][2],lst2[2][2];
for(int i=0;i<2;i++) for(int j=0;j<2;j++) lst1[i][j]=lst2[i][j]=n+1;
for(int i=n;i>=1;i--){
int t=s[i]-'0';
trans[i][t^1]=lst1[(i&1)^1][t^1];
trans[i][t]=lst2[(i&1)^1][t];
lst1[i&1][t]=i;
if(s[i-1]==s[i]) lst2[i&1][t]=i;
}
for(int i=1;i<=n;i++) f[i]=0;
f[1]=f[lst2[1][(s[1]-'0')^1]]=1;
int ans=0;
for(int i=1;i<=n;i++){
for(int c=0;c<2;c++) add(f[trans[i][c]],f[i]);
if((i&1)==(n&1)&&(s[i]==s[n]||trans[i][s[i]-'0']!=n+1)) add(ans,f[i]);
}
printf("%d\n",ans);
}
int main(){
int T=1;
scanf("%d",&T);
__INIT__();
while(T--) __SOLVE__();
return 0;
}
汤圆的神秘做法。写出单个段的判定自动机,注意到 \(0,2,1,3\) 间严格偏序。尝试不从 \(0\) 开始,即(除首尾段外)每段首个字符为目标字符,发现可以做到。
???+ 证明
关注一个不满足要求的段,考虑这个段和其上一个段的目标字符。
若相同,假设均为 \(1\),则字符没有被 flip 过,第二段从 \(0\) 结点开始,意味着中间必经过至少一次 \(11\),关注第一次,在这两个 \(1\) 中间劈开作为新的分界点,则第一段多了从 \(1/3\) 开始走若干步最后到 \(1\) 的过程,由于 \(2\) 的 \(?1\) 出边都是 \(1\),故合法;第二段原本是从 \(1\) 开始继续往后走,现在仍是,也合法。
若不同,假设第一段为 \(0\),第二段为 \(1\),由于 \(1\) 结点走 \(1?\) 到 \(3\) 结点,进了 \(3\) 结点就永远合法,故随便往后调到一个第二段合法的位置,第一段都仍合法。
???-
于是变成了与正常做法本质相同的东西。
*QOJ7083 泰然自若
神秘 DP of DFA 题。
不难想到一个判定背包,记 \(w=9\),注意到只需要保留值 \([0,w^2-1]\) 的状态(证明见论文例题 5.2更进一步可以分析到 \(9\times(9-1)=72\),但我不会),可以写出一个状态数 \(2^{73}\) 的 DFA(维护哪些状态是 \(1\),本质上是 NFA 转 DFA),暴搜发现只有 \(\mathcal{O}(10^4)\) 个可达状态,然后跑 DFA 最小化只剩下 \(715\) 个状态,DP of DFA 即可。
P6667 [清华集训 2016] 如何优雅地求和
本人的神秘做法,好像也不是很优(\(\mathcal{O}(m\log^2m)\))。
做一个快速插值(这里是瓶颈,\(\mathcal{O}(m\log^2m)\),用分治做拉插),得到 \(f\) 系数表达。
不难想到:
\(S_i=\sum_{k=0}^nk^i\binom{n}{k}x^k(1-x)^{n-k}\) 形如 \(k\) 次方和,考虑经典操作:
于是 \(\mathcal{O}(m\log m)\) 多项式快速幂求出 \(S_i\) 即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353,G=3;
void add(int &x,int y){
x+=y;
if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
x+=y;
if(x>=MOD) x-=MOD;
return x;
}
int qpow(int a,ll b){
int mul=1;
while(b){
if(b&1) mul=(ll)mul*a%MOD;
a=(ll)a*a%MOD;
b>>=1;
}
return mul;
}
const int M=40005;
int fact[M],invfact[M],invv[M];
int stand(int n){
int len;
for(len=1;len<n;len<<=1);
return len;
}
void ntt(vector<int> &f,bool flag=false){
int n=f.size();
vector<int> rev(n);
for(int i=1;i<n;i++){
rev[i]=rev[i>>1]>>1;
if(i&1) rev[i]|=(n>>1);
}
for(int i=1;i<n;i++) if(rev[i]<i) swap(f[rev[i]],f[i]);
for(int w=1;w<n;w<<=1){
int step=qpow(G,(MOD-1)/(w<<1));
if(flag) step=qpow(step,MOD-2);
for(int i=0;i<n;i+=(w<<1)){
for(int j=0,cur=1;j<w;j++,cur=(ll)cur*step%MOD){
int a=f[i+j],b=(ll)cur*f[i+j+w]%MOD;
f[i+j]=add2(a,b);
f[i+j+w]=add2(a,MOD-b);
}
}
}
if(flag){
int inv=qpow(n,MOD-2);
for(int i=0;i<n;i++) f[i]=(ll)f[i]*inv%MOD;
}
}
void print(const vector<int> &f,const string &prompt){
cout<<prompt<<" ";
for(auto i:f) printf("%d ",i);printf("\n");
}
vector<int> inv(vector<int> f,int n=-1){
if(n==-1) n=f.size();
else f.resize(n);
if(n==1) return {qpow(f[0],MOD-2)};
vector<int> g=inv(f,n>>1);
g.resize(n<<1),f.resize(n<<1);
ntt(g),ntt(f);
for(int i=0;i<(n<<1);i++) g[i]=add2(add2(g[i],g[i]),MOD-(ll)g[i]*g[i]%MOD*f[i]%MOD);
ntt(g,1);
g.resize(n);
return g;
}
vector<int> ln(vector<int> f){
int n=f.size();
f.resize(stand(n));
vector<int> g=inv(f);
for(int i=1;i<f.size();i++) f[i-1]=(ll)i*f[i]%MOD;
f.pop_back();
int len=stand(g.size()+f.size()-1);
f.resize(len),g.resize(len);
ntt(f),ntt(g);
for(int i=0;i<len;i++) f[i]=(ll)f[i]*g[i]%MOD;
ntt(f,1);
f.resize(n);
for(int i=f.size()-1;i>0;i--) f[i]=(ll)invv[i]*f[i-1]%MOD;
f[0]=0;
return f;
}
vector<int> exp(vector<int> f,int n=-1){
if(n==-1) n=f.size();
else f.resize(n);
if(n==1) return {1};
vector<int> g=exp(f,n>>1);
// printf("exp\n");
// print(f,"f");
g.resize(n);
// print(g,"g0");
vector<int> h=ln(g);
// print(h,"ln(g0)");
g.resize(n<<1),h.resize(n<<1),f.resize(n<<1);
ntt(g),ntt(h),ntt(f);
for(int i=0;i<(n<<1);i++) g[i]=(ll)g[i]*(MOD+1+f[i]-h[i])%MOD;
ntt(g,1);
g.resize(n);
// print(g,"g");
return g;
}
vector<int> pow(vector<int> f,int k){
int n=f.size(),len=stand(n);
f.resize(len);
f=ln(f);
for(int i=0;i<len;i++) f[i]=(ll)k*f[i]%MOD;
f=exp(f);
f.resize(n);
return f;
}
int n,m,x;
vector<int> a,f;
vector<int> mul(vector<int> f,vector<int> g){
int n=f.size()+g.size()-1,len=stand(n);
f.resize(len),g.resize(len);
ntt(f),ntt(g);
for(int i=0;i<len;i++) f[i]=(ll)f[i]*g[i]%MOD;
ntt(f,1);
f.resize(n);
return f;
}
vector<int> add(vector<int> f,vector<int> g){
int n=max(f.size(),g.size());
f.resize(n);
for(int i=0;i<g.size();i++) add(f[i],g[i]);
return f;
}
pair<vector<int>,vector<int>> Lagrange(const vector<int> &f,int l,int r){
if(l==r){
int k=(ll)f[l]*invfact[l]%MOD*invfact[f.size()-l-1]%MOD;
if((f.size()-l-1)&1) k=MOD-k;
return {{k},{MOD-l,1}};
}
int mid=(l+r)>>1;
auto [fl,gl]=Lagrange(f,l,mid);
auto [fr,gr]=Lagrange(f,mid+1,r);
// printf("[%d,%d]\n",l,r);
// print(fl,"fl"),print(gl,"gl");
// print(fr,"fr"),print(gr,"gr");
return {add(mul(fl,gr),mul(gl,fr)),mul(gl,gr)};
}
void __INIT__(){
fact[0]=1;
for(int i=1;i<M;i++) fact[i]=(ll)i*fact[i-1]%MOD;
invfact[M-1]=qpow(fact[M-1],MOD-2);
for(int i=M-1;i>0;i--) invfact[i-1]=(ll)i*invfact[i]%MOD;
for(int i=1;i<M;i++) invv[i]=(ll)invfact[i]*fact[i-1]%MOD;
}
int main(){
__INIT__();
scanf("%d%d%d",&n,&m,&x);
a.resize(m+1);
for(int i=0;i<=m;i++) scanf("%d",&a[i]);
// print(a,"a");
a=Lagrange(a,0,m).first;
// print(a,"a");
f.resize(m+1);
for(int i=0;i<=m;i++) f[i]=(ll)invfact[i]*x%MOD;
add(f[0],MOD+1-x);
f=pow(f,n);
int ans=0;
for(int i=0;i<=m;i++) add(ans,(ll)f[i]*fact[i]%MOD*a[i]%MOD);
printf("%d\n",ans);
return 0;
}
P10770 「CROI · R2」夏风与树
见 2026.06 模拟赛
*QOJ12115 Chips-dips
from 2026.06 模拟赛
注意到操作不改变逆序对数量,尝试以此为 YES/NO 判据,发现不充要,进一步发现 min,max 相对位置不变,以这两个为判据,发现充要了,必要性显然,充分性可以构造性证明。
由于 \(a\) 直接移动到 \(b\) 不好做,发现操作可逆,于是考虑把它们同时操作为 \(c\)。
尝试对一个排列 \(p\),把 \(p\) 中的 max 移动到两端(min 同理可得)。首先发现如果 min,max 相邻,则二者可以绑在一起任意移动;如果不相邻,设 min 在 x,max 在 y,转为 \(p[x+1:|p|]\) 或 \(p[1:x-1]\) 的子问题,取决于 \(x,y\) 大小关系,子问题处理完后,要么 max 在两端,要么 max 与 min 相邻,若相邻,绑在一起移动到两端。
于是若 \(a,b\) 中 min,max 相对位置相同,把靠右的那个移动到两端,因为相对位置的限制,所以只能被移动到右端,从而转为 \(a[1:n-1],b[1:n-1]\) 的子问题。
若 \(a,b\) 中 min,max 相对位置不同,只能想办法交换 min,max 位置,显然需要依赖右端已经定下来的数,这个数等价于 \(+\infty/-\infty\),尝试把 \(\min,\max,+\infty\rightarrow\min,+\infty,\max\)(或大小反转),于是发现如下操作模式:
\(y,x,\min,\max,+\infty\rightarrow y,\min,\max,x,+\infty\rightarrow y,\min,x,+\infty,\max\rightarrow x,y,\min,+\infty,\max\rightarrow x,y,\max,\min,+\infty\),要求 \(\min<x<y<\max\)。或大小反转。
我们声称,\(a,b\) 中总有一个可以做到,从而把 \(a,b\) 右端变为相同。
???+ 证明
先证明对一个长 \(n\) 排列 \(p\),如果逆序对数量 \(\pi(p)\ge n-1\),且 \(p\) 形如 ...1...n...,则可在结尾有 \(+\infty\) 的情况下被操作为 ...n1。
归纳,对 \(n=4\) 显然成立。先把 \(n\) 移至最后。若 \(n-1\) 在 \(1\) 左侧,则直接在 \([1:n-1]\) 中把 \(1\) 移至右端,\(p\) 变为 ...1n。否则,规约至 \(n-1\) 子问题,由于 \(\pi(p)\ge n-1>n-2\),则 \(p\) 可变为 ...1n。
\(1\) 会产生 \(n-2\) 个逆序对,去掉后还剩至少一个,说明 ... 非递增,则可以拉过来一个 \((y,x)\) 满足 \(y>x\),进行上面的操作。拉的方法:从一个 \((y,x)\) 满足 \(y>x\) 出发,考虑下一个 \(z\):若 \(y>x>z\),则变为 \((x,z)\);若 \(y>z>x\),则把 \(z\) 左移 \(2\),仍为 \((y,x)\);若 \(z>y>x\),则把 \(y\) 右移 \(2\),变为 \((z,y)\)。
不妨设 \(a,b\) 右侧已经定下来的数为 \(+\infty\)(\(-\infty\) 则大小反转),不妨设 \(a\) 为 \(\dots,\min,\dots,\max,\dots\),\(b\) 为 \(\dots,\max,\dots,\min,\dots\),则不难发现 \(\pi(b)\ge|b|-1\)(左侧的数与 \(\min\) 配对,右侧与 \(\max\) 配对),由于 \(\pi(a)=\pi(b),|a|=|b|\),则 \(\pi(a)\ge |a|-1\),故 \(a,b\) 中 min 均可被移到右端。
???-
不断按上述方式规约,同时维护操作序列,最后把 \(a,b\) 操作序列拼起来即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
typedef pair<ll,ll> pll;
template<typename T>
void chkmin(T &x,const T &y){x=min(x,y);}
template<typename T>
void chkmax(T &x,const T &y){x=max(x,y);}
const int inf=0x3f3f3f3f;
const ll infll=0x3f3f3f3f3f3f3f3f;
const int MOD=998244353;
void add(int &x,int y){
x+=y;
if(x>=MOD) x-=MOD;
}
int add2(int x,int y){
x+=y;
if(x>=MOD) x-=MOD;
return x;
}
int qpow(int a,ll b){
int mul=1;
while(b){
if(b&1) mul=(ll)mul*a%MOD;
a=(ll)a*a%MOD;
b>>=1;
}
return mul;
}
const int N=205;
int n,p[2][N],inv[2][N];
vector<pii> ans[2];
int cnt(int t){
int ans=0;
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++) ans+=(p[t][i]>p[t][j]);
}
return ans;
}
void step(int t,int i,int d){
// printf("step %d %d %d\n",t,i,d);
ans[t].push_back({i,d});
if(d>0) for(int j=i;j<i+d;j++) swap(inv[t][p[t][j]],inv[t][p[t][j+1]]),swap(p[t][j],p[t][j+1]);
else for(int j=i;j>i+d;j--) swap(inv[t][p[t][j]],inv[t][p[t][j-1]]),swap(p[t][j],p[t][j-1]);
}
// return: 0-left 1-right
int solve1(int l,int r,int v,int type,int t){
// printf("l=%d r=%d v=%d type=%d t=%d\n",l,r,v,type,t);
// for(int i=l;i<=r;i++) printf("%d ",p[t][i]);printf("\n");
assert(l<=r);
if(p[t][l]==v) return 0;
if(p[t][r]==v) return 1;
int x;
if(!type){
pii mn{inf,-1};
for(int i=l;i<=r;i++) chkmin(mn,{p[t][i],i});
x=mn.second;
}
else{
pii mx{-inf,-1};
for(int i=l;i<=r;i++) chkmax(mx,{p[t][i],i});
x=mx.second;
}
if(x<inv[t][v]&&!solve1(x+1,r,v,type,t)) for(int i=x+2;i<=r;i++) step(t,i,-2);
if(x>inv[t][v]&&solve1(l,x-1,v,type,t)) for(int i=x-2;i>=l;i--) step(t,i,2);
// printf("solve1 ");for(int i=l;i<=r;i++) printf("%d ",p[t][i]);printf("\n");
return inv[t][v]==r;
}
void solve2(int l,int r,int mn,int mx,int type,int t){
// printf("solve2 %d %d %d %d %d %d\n",l,r,mn,mx,type,t);
// for(int i=l;i<=r;i++) printf("%d ",p[t][i]);printf("\n");
int v1,v2;
if(!type) v1=mn,v2=mx;
else v1=mx,v2=mn;
if(inv[t][v1]>inv[t][v2]){
if(!type) solve1(l,r,mn,1,t);
else solve1(l,r,mx,0,t);
return;
}
solve1(l,r,v2,type,t);
solve2(l,r-1,mn+type,mx-!type,type,t);
bool flag=false;
for(int i=l;i<r-2;i++){
if(flag){
if((p[t][i+1]>p[t][i-1])^type) step(t,i-1,2);
else if((p[t][i+1]>p[t][i])^type) step(t,i+1,-2);
}
if((p[t][i]>p[t][i+1])^type) flag=true;
}
step(t,r-2,2),step(t,r-1,2),step(t,r-1,-2),step(t,r+1,-2);
}
void __INIT__(){}
void __SOLVE__(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&p[0][i]),inv[0][p[0][i]]=i;
for(int i=1;i<=n;i++) scanf("%d",&p[1][i]),inv[1][p[1][i]]=i;
int mn=1,mx=n;
if((inv[0][mn]<inv[0][mx])!=(inv[1][mn]<inv[1][mx])||cnt(0)!=cnt(1)){
printf("NO\n");
return;
}
for(int i=n;i>=1;i--){
// printf("i=%d\n",i);
// for(int j=1;j<=n;j++) printf("%d ",p[0][j]);printf("\n");
// for(int j=1;j<=n;j++) printf("%d ",p[1][j]);printf("\n");
bool flag=true;
for(int j=1;j<=i;j++) if(p[0][j]!=p[1][j]){
flag=false;
break;
}
if(flag) break;
if((inv[0][mn]<inv[0][mx])==(inv[1][mn]<inv[1][mx])){
if(inv[0][mn]<inv[0][mx]) solve1(1,i,mx,0,0),solve1(1,i,mx,0,1),mx--;
else solve1(1,i,mn,1,0),solve1(1,i,mn,1,1),mn++;
}
else{
if(p[0][i+1]>mx){
int t=inv[1][mn]<inv[1][mx];
solve2(1,i,mn,mx,0,t),solve1(1,i,mn,1,!t);
mn++;
}
else{
int t=inv[1][mn]>inv[1][mx];
solve2(1,i,mn,mx,1,t),solve1(1,i,mx,0,!t);
mx--;
}
}
}
printf("YES\n%d\n",ans[0].size()+ans[1].size());
for(auto [x,v]:ans[0]) printf("%d %d\n",x,v);
for(int i=ans[1].size()-1;i>=0;i--){
auto [x,v]=ans[1][i];
printf("%d %d\n",x+v,-v);
}
}
int main(){
#ifndef JZQ
freopen("construction.in","r",stdin);
freopen("construction.out","w",stdout);
#endif
int T=1;
// scanf("%d",&T);
__INIT__();
while(T--) __SOLVE__();
return 0;
}
然而操作总数不 \(\le 10^6\),有如下 hack,操作总数 \(1313400\),除非把输入添加随机扰动,几乎无法通过,故本题算半道错题。
200
1 3 5 7 ... 199 200 ... 6 4 2
199 197 ... 3 1 2 4 6 ... 198 200
让我们来证明复杂度吧!
我们称直接把 min,max 移动到两端为 solve1,把 min,max 移到两端并交换为 solve2,拉过来一组 \((y,x)\) 为 solve3。
粗略的分析发现,solve1 为 \(\mathcal{O}(n^2)\),solve3 为 \(\mathcal{O}(n)\),solve2 最多递归 \(n\) 层,每层调一次 solve1,solve3,为 \(\mathcal{O}(n^3)\),全流程最多调 \(n\) 次 solve2,为 \(\mathcal{O}(n^4)\),但实际上卡不到。
首先 solve1 确实是 \(\mathcal{O}(n^2)\),在下面的结构中可被卡满(\(\frac12\) 常数):
1 3 5 7 ... n ... 6 4 2
观察 solve2,发现整体来看,一波 solve2 的调用形如
... --一系列 solve1 --> ... 1 k k+1 k+2 ... n(... 1 中的数 \(<k\)) -> ... n 1。
我们声称,1 穿过 k k+1 ... n 的过程不会破坏这个连续结构,手玩一下:
| 操作 | 状态 |
|---|---|
| 无 | ... y x 1 k k+1 ... n |
| swap | ... x y k 1 k+1 ... n |
| solve3-part1 | ... y' x' k 1 k+1 ... n |
| solve3-part2 | ... x' k y' 1 k+1 ... n |
| swap | ... x' y' k k+1 1 k+2 ... n |
| solve3-part1 | ... y'' x'' k k+1 1 k+2 ... n |
| solve3-part2 | ... x'' k y'' k+1 1 k+2 ... n |
| solve3-part3 | ... x'' y'' k+1 k 1 k+2 ... n |
| swap | ... x'' y'' k k+1 k+2 1 k+3 ... n |
| solve3-part1 | ... y''' x''' k k+1 k+2 1 k+3 ... n |
| solve3-part2 | ... x''' y''' k k+2 k+1 1 k+3 ... n |
| swap | ... x''' y''' k k+1 k+2 k+3 1 ... n |
| ... | ... |
| swap | ... k k+1 ... n 1 |
可发现规律,不难把规律严格化(这真不难吧),这里懒了。
观察这个 k ... n 的段,观察不同操作对他的影响:
- 直接 solve1,拿掉结尾的 n(max),其长度 \(-1\),这个段长度减到 \(0\) 时,有可能发生大小反转。
- solve2,此时必定是把 1(min) 移到 n(max) 左侧,然后交换,其中每调用一次 solve1,这个段长度至少 \(+1\)。
重点关注 solve2 中调用的 solve1,其余部分粗略分析就是对的。我们设上面这样 k...n 的段长度为势能,每增加 \(1\) 会产生 \(n^2\) 的代价,增加量不超过 \(n\),总复杂度 \(\mathcal{O}(n^3)\)。

浙公网安备 33010602011771号