点此看题
\(\tt oneindark\) 真的离谱,这种题都能切呢?\(3300\) 的题都能切呢?!
首先我们应用 \(01\) 原则,\(\forall i\),我们把前 \(i\) 小的数变成 \(0\),剩下的数变成 \(1\),然后对这个数列排序,所有排序次数取最大值就是答案。每次得到的条件是前 \(i\) 小的数被排好序了,所以大家都被排好序了。
结论:\(01\) 序列的排序次数为某个在 \(0\) 前面的 \(1\),其前面 \(1\) 的个数\(+\)后面 \(0\) 的个数\(+[\)这是否是偶数位置\(]\)的最大值。
考虑 \(f(i,j)\) 表示第 \(i\) 个 \(1\) 越过第 \(j\) 个 \(0\) 的排序次数,转移:
\[f(i,j)=\max(f(i+1,j),f(i,j-1))+1 \]这其实相当于图上求最长链,那么我们考虑每个叶子的最长链即可,每次可以向左使 \(i-1\),向右使 \(j+1\),初值和位置 \(i\) 的奇偶性有关,然后加上前面 \(1\) 的个数和后面 \(0\) 的个数即可。
因为这个函数定义在 \(1\) 上所以只能在 \(1\) 位置取最值,因为只有在 \(0\) 前面的 \(1\) 有定义所以只能考虑它。
用线段树把上面的结论模拟出来即可,时间复杂度 \(O(n\log n)\)
排序问题可以转 \(01\) 序列,注意 \(01\) 原则的应用即可。
本题反复取 \(\max\) 也是有趣之处,主要思想是把困难的问题拆成若干独立子问题。
#include <cstdio> #include <cstring> #include <iostream> using namespace std; const int M = 200005; const int inf = 0x3f3f3f3f; int read() { int x=0,f=1;char c; while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;} while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();} return x*f; } int T,n,ans,b[M],mx[4*M],fl[4*M]; void jzm(int i,int c) { mx[i]+=c;fl[i]+=c; } void down(int i) { jzm(i<<1,fl[i]); jzm(i<<1|1,fl[i]); fl[i]=0; } void build(int i,int l,int r) { mx[i]=fl[i]=0; if(l==r) { mx[i]=-inf+(l%2==0)+l-1; return ; } int mid=(l+r)>>1; build(i<<1,l,mid); build(i<<1|1,mid+1,r); mx[i]=max(mx[i<<1],mx[i<<1|1]); } void add(int i,int l,int r,int L,int R,int c) { if(L>r || l>R) return ; if(L<=l && r<=R) {jzm(i,c);return ;} int mid=(l+r)>>1;down(i); add(i<<1,l,mid,L,R,c); add(i<<1|1,mid+1,r,L,R,c); mx[i]=max(mx[i<<1],mx[i<<1|1]); } signed main() { T=read(); while(T--) { n=read();ans=0; for(int i=1;i<=n;i++) b[read()]=i; build(1,1,n); for(int i=1,j=1;i<=n;i++)//1->0 { int x=b[i]; for(;j<=x;j++) add(1,1,n,j,j,inf); add(1,1,n,x,x,-inf); add(1,1,n,x,n,-1); add(1,1,n,1,x-1,1); ans=max(ans,mx[1]); } printf("%d\n",ans); } }