AtCoder Grand Contest 001 F permutation

permutation

Description

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

Data Constraint

n <=5105

Solution

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

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

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

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

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

注:上述所述的连边都是指qi qj 间的连边,并不是 i j之间的连边,是 q <script type="math/tex" id="MathJax-Element-15501">q</script>序列中值与值的连边,并不是位置编号与位置编号的连边。

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();
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值