题目大意
给出一个1~N的排列,求一个逆序对数等于原序列,且字典序大于原序列、字典序最小的排列
N<=500000
N
<=
500000
首先,显然可以一个一个排列往下推,直到找到一个逆序对数等于原序列的排列
(C++ STL
′algorithm′
′
a
l
g
o
r
i
t
h
m
′
头文件中有个next_permutation()函数,就是求按字典序的下一个排列的,用法同sort())
当然TLE到飞起,只有36分
(PS:虽然正解写挂的我只有20分!!!)
下面考虑正解:
我们知道,数列的字典序比较是自左向右,比较每一个数,根据两数列第一个数值不同的相同位置的大小关系,来确定两个数列的字典序大小
那么,贪心的想法,为了字典序尽量小,尽量不要改动前面,尽量将改动的地方延后,且改动后的数字要尽量小——为了逆序对数不变,后面的自然要重新排列,增加逆序对数来平衡
又由于一个没有重复元素的序列的集合的逆序对数
F[S]<=|S|∗(|S|−1)/2
F
[
S
]
<=
|
S
|
∗
(
|
S
|
−
1
)
/
2
(降序排列时最大),所以最后的“核心改动位置”
X
X
就可以确定下来了
字典序要变大,就用A[X+1~N]中最小的大于A[X]的A[Y](upper_bound)替换掉A[X]
接下来对于A[X+1~N]进行重组,使其在满足所求逆序对数的情况下字典序最小
(事实上,根据贪心原理,只要再二分(或直接枚举)找一个位置A[Z]进行替换,再将A[Z+1~N]降序排列即可——证明同上贪心原理)
然后就解决了此题,具体一些细节见注释
(PS:如果先做几趟预处理,将所需的值先处理好,
O(N)
O
(
N
)
也可以下来)
#include<cstdio>
#include<algorithm>
#include<set>
#define LL long long
using namespace std;
const int maxn=(5e5)+5;
int n,a[maxn];LL A,B,c[2][maxn],cnt[maxn];
void add(int k,int data,bool pp){while(k<=n) c[pp][k]+=data,k+=k&-k;}
LL get(int k,bool pp){LL S=0;while(k) S+=c[pp][k],k-=k&-k;return S;}
struct ff{
int x,id;
bool operator <(const ff b)const{return x<b.x;}
}tep;
set<ff>p;
set<ff>::iterator it;
char gt(){
static char buf[100000],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
int read(){
int ret=0;char ch=gt();
while(ch<'0'||ch>'9') ch=gt();
while(ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=gt();
return ret;
}
void write(int x){if(x<10) putchar(x+'0');else write(x/10),putchar(x%10+'0');}
int find(int x,LL y){
int L=x,R=n,mid;
while(L<=R){
mid=L+R>>1;
if((LL)(n-mid+1)*(n-mid)/2>=y) L=mid+1;else R=mid-1;
}
return R;
}
int main(){
n=read();
for(int i=1;i<=n;i++) a[i]=read(),cnt[i]=(A+=i-1-get(a[i]-1,0)),add(a[i],1,0);
tep.x=n+1,p.insert(tep);
int y,nn;bool flg=1;
for(int i=n;i;i--){
add(a[i],1,1),B-=get(a[i]-1,1);
add(a[i],-1,0),B+=i-1-get(a[i]-1,0);
tep.x=a[i],tep.id=i;p.insert(tep);
LL now=cnt[i-1]+B+(LL)(n-i+1)*(n-i)/2,fuck;
if(now>=A){//逆序对数最大值
tep.x=a[i-1],tep.id=i-1;
it=p.upper_bound(tep);
if((*it).x!=n+1){
fuck=cnt[i-1]+B+get(a[(*it).id]-1,1)+1-get(a[i-1]-1,1);
if(fuck>A) continue;//逆序对数最小值
add(a[i-1],-1,0),add(a[(*it).id],1,0);
swap(a[i-1],a[(*it).id]),y=i;
break;
}
}
}
sort(a+y,a+1+n);
B=cnt[y-2]+y-2-get(a[y-1]-1,0);
for(int i=y;i<=n;i++) B+=y-1-get(a[i]-1,0);//前面y个位置的数的固定贡献
y=find(y,A-B),nn=A-B-(LL)(n-y)*(n-y-1)/2+y;
swap(a[y],a[nn]),sort(a+1+y,a+1+n,greater<int>());
for(int i=1;i<n;i++) write(a[i]),putchar(' ');write(a[n]),putchar('\n');
return 0;
}