从全是0和1的情况入手,可以像线段树一样分治下去,回到本层的时候就是左半部的右边是1,右半部的左边是0,把这两部分换一下就行。代价和时间一样是nlogn。
不全是0和1,可以像快速排序一样,先找一个基准,然后小于它的是0、大于它的是1,调用上一行的那个函数;本层弄好0和1以后,递归到全是0的部分和全是1的部分即可。这样代价和时间都是nlog^2n。
那个基准找得不好的话,一不小心就陷入死循环。所以自己还专门unique了一下,确保不会递归到自己。不过还是很心虚。
看别人有很好的写法,就是以基准(它的值也是中间位置的值,但不用unique)为mid,调用另一个和递归自己的范围就都可以是 l,mid-1 和 mid+1,r 了,这样就不会死循环。而且那个人没有返回那个0和1的边界,而是每次现从mid开始找;只是觉得这样写法扩展自己思路。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N=5e4+5; int n,a[N],tmp[N],top; bool b[N]; int rdn() { int ret=0;bool fx=1;char ch=getchar(); while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();} while(ch>='0'&&ch<='9') ret=(ret<<3)+(ret<<1)+ch-'0',ch=getchar(); return fx?ret:-ret; } int deb; int calc(int l,int r) { // if(deb<=30)printf("calc l=%d r=%d bl=%d ",l,r,b[l]),deb++; int p=r+1; for(int i=l;i<=r;i++) if(b[i]){p=i;break;} if(p>r) return p-1;//all 0 p=l-1; for(int i=l;i<=r;i++) if(!b[i]){p=i;break;} if(p<l)return p;//all 1 int mid=l+r>>1; int p0=calc(l,mid),p1=calc(mid+1,r); // if(deb<=30)printf("calc:l=%d r=%d p0=%d p1=%d ",l,r,p0,p1); if(p0+1<p1) { printf("%d %d ",p0+1,p1); for(int i=p0+1,j=p1;i<j;i++,j--) swap(a[i],a[j]),swap(b[i],b[j]); } p=r+1; for(int i=l+1;i<=r;i++) if(b[i]){p=i-1;break;} // if(deb<=30)printf("p=%d ",p); return p; } void solve(int l,int r) { if(l>=r)return; bool flag=0; for(int i=l+1;i<=r;i++) if(a[i]!=a[i-1]){flag=1;break;} if(!flag)return; top=0; for(int i=l;i<=r;i++) tmp[++top]=a[i]; sort(tmp+1,tmp+top+1); top=unique(tmp+1,tmp+top+1)-tmp-1;// int base=tmp[top>>1]; // if(deb<=30)printf("base=%d ",base); for(int i=l;i<=r;i++) b[i]=(a[i]>base);//配合下取整的top /* if(deb<=30) { printf("psol "); for(int i=l;i<=r;i++)printf("%d ",b[i]); printf(" "); } */ // if(deb<=30)printf("solve l=%d r=%d ",l,r); int d=calc(l,r); // if(deb<=30)printf("d=%d ",d); /* if(deb<=30) { printf("csol "); for(int i=l;i<=r;i++)printf("%d ",b[i]); printf(" "); } */ solve(l,d); solve(d+1,r); } int main() { n=rdn(); for(int i=1;i<=n;i++) a[i]=rdn(); solve(1,n); printf("-1 -1 "); return 0; }