乐曲创作——题解

题目大意

给出一个1~N的排列,求一个逆序对数等于原序列,且字典序大于原序列字典序最小的排列
N <= 500000

首先,显然可以一个一个排列往下推,直到找到一个逆序对数等于原序列的排列
(C++ STL a l g o r i t h m 头文件中有个next_permutation()函数,就是求按字典序的下一个排列的,用法同sort()
当然TLE到飞起,只有36分
(PS:虽然正解写挂的我只有20分!!!)

下面考虑正解:
我们知道,数列的字典序比较是自左向右,比较每一个数,根据两数列第一个数值不同的相同位置的大小关系,来确定两个数列的字典序大小
那么,贪心的想法,为了字典序尽量小,尽量不要改动前面,尽量将改动的地方延后,且改动后的数字要尽量小——为了逆序对数不变,后面的自然要重新排列,增加逆序对数来平衡
又由于一个没有重复元素的序列的集合的逆序对数 F [ S ] <= | S | ( | S | 1 ) / 2 (降序排列时最大),所以最后的“核心改动位置” X 就可以确定下来了
字典序要变大,就用A[X+1~N]中最小的大于A[X]的A[Y](upper_bound)替换掉A[X]
接下来对于A[X+1~N]进行重组,使其在满足所求逆序对数的情况下字典序最小
(事实上,根据贪心原理,只要再二分(或直接枚举)找一个位置A[Z]进行替换,再将A[Z+1~N]降序排列即可——证明同上贪心原理
然后就 O ( N l o g ) 解决了此题,具体一些细节见注释
(PS:如果先做几趟预处理,将所需的值先处理好, 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;
}

猜你喜欢

转载自blog.csdn.net/qq_42403731/article/details/81036901