AtCoder Grand Contest 001 F permutation

来源:互联网 发布:c语言程序 编辑:程序博客网 时间:2024/06/08 05:12

permutation

Description

你有一个长度为n的排列P与一个正整数K
你可以进行如下操作若干次使得排列的字典序尽量小
对于两个满足|i-j|>=K 且|Pi-Pj|=1的下标ij,交换PiPj

Data Constraint

n<=5105

Solution

转换问题,令qPi=i,题目变成了令q的字典序最小,最后再把答案序列按类似的转换方法还原回去即可。
一种合法的操作定义为,若q中相邻的两个位置上的数的差的绝对值大于等于K,便可交换这两个位置上的数。

我们可以观察出一个性质,对于两个数i,j(i<j),若|qi-qj|<K,那么qj永远不可能换到qi的前面,也就意味着qi这个数 永远都在qj的前面。
根据这个性质,对于所有满足条件的i,j,让qiqj连上一条边,意义为qi必须先选到答案序列 中,才可再选qj,这样便会建出一个DAG图,对DAG跑一边字典序最小 的拓扑序,便可以求出字典序最小的答案序列。

当时上述的做法理论上连边数回达到n2级别,会超时。
事实上我们每个点可以只连两条边,对于每个qi,只向它右边数起第一个比qi 的、满足条件的(即满足qj-qi<K)的qj连边,和向它右边数起第一个比qi 的、满足条件的(即满足qi-qj<K)的qj连边,易证,这样建出来的DAG图,跑出来的拓扑序和n2级别的算法跑出来的拓扑序是一样的。

对于每个qi,找到它右边满足条件的两个数,用一棵线段树维护即可。时间复杂度O(n log n)。

在构造答案序列的时候,用一个set维护当前入度已为0qi,边构造边模拟拓扑序的进行(记得要字典序小的点先跑)。
每一次从set中选出一个最小的qi选入答案序列中,同时删去与qi相连的边,并检查是否有新的点入度刚变为0,如有,将这个点加入set即可。
当然,也可以不用set,用C++其他的黑科技也行。

注:上述所述的连边都是指qiqj间的连边,并不是ij之间的连边,是q序列中值与值的连边,并不是位置编号与位置编号的连边。

Code

#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<set>#define fo(i,j,l) for(int i=j;i<=l;i++)#define fd(i,j,l) for(int i=j;i>=l;i--)#define rep(i,j) for(int i=la[j];i;i=ne[i])using namespace std;typedef long long ll;const ll N=5e5+1e3,M=2*N;int n,m,k,l,o,a,w1,w2,oo,kk;int q[N],ne[M],lb[M],la[N],t[M*2],d[N],ans[N],p[N];set<int> op;void read(int &o){    o=0; char ch=' ';    for(;ch<'0'||ch>'9';)ch=getchar();    for(;ch>='0'&&ch<='9';ch=getchar())o=o*10+ch-48;}void write(int o){    int kt=0; char ch[20];    for(;o;o/=10)ch[++kt]='0'+o%10;    fd(i,kt,1)printf("%c",ch[i]);    printf("\n");}int min(int a,int b){if(a<b)return a;else return b;}int max(int a,int b){if(a>b)return a;else return b;}void jl(int o,int l,int r){    t[o]=n+1; if(l==r)return;    int mid=(l+r)/2;    jl(o*2,l,mid); jl(o*2+1,mid+1,r); }void change(int o,int l,int r){    if(l==r&&l==w1){t[o]=w2; return;}    if(l>w1||r<w1)return;    int mid=(l+r)/2;    change(o*2,l,mid); change(o*2+1,mid+1,r);    if(t[o*2]<t[o*2+1])t[o]=t[o*2];else t[o]=t[o*2+1];}void question(int o,int l,int r){    if(l>=w1&&r<=w2){        if(t[o]<kk)kk=t[o];        return;    }    if(l>w2||r<w1)return;    int mid=(l+r)/2;    question(o*2,l,mid); question(o*2+1,mid+1,r);}void llb(int a,int b){ne[++oo]=la[a]; lb[oo]=b; la[a]=oo; d[b]++;}void doing(){    jl(1,1,n);    fd(i,n,1){        kk=n+1; w1=p[i]; w2=min(p[i]+k-1,n);        question(1,1,n);        if(kk!=n+1)llb(p[i],p[kk]);        kk=n+1; w1=max(1,p[i]-k+1); w2=p[i];        question(1,1,n);        if(kk!=n+1)llb(p[i],p[kk]);        w1=p[i]; w2=i;        change(1,1,n);    }    fo(i,1,n)if(!d[i])op.insert(i);    fo(i,1,n){        int y=ans[i]=*op.begin(); op.erase(y);        rep(j,y)        if(!(--d[lb[j]]))op.insert(lb[j]);    }    fo(i,1,n)p[ans[i]]=i;    fo(i,1,n)write(p[i]);}void intt(){    cin>>n>>k;    fo(i,1,n)    read(a),p[a]=i;}int main(){    intt();    doing();}