【NOIP2017提高A组模拟10.10】Permutation

Description

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

Input

第一行包括两个正整数n 与K
第二行包括n 个正整数,第i 个正整数表示Pi

Output

输出一个新排列表示答案
输出共n 行,第i 行表示Pi

Sample Input

8 3
4 5 7 8 3 1 2 6

Sample Output

1
2
6
7
5
3
4
8

Solution

转换一下题意
若以p[i]为下标,i为值
就是q[p[i]]=i
对于q做修改,那么必须满足的条件就是,两个数相邻可以交换,并且这两个数之差的绝对值>=k
要使p字典序最小,即q字典序最小
那么小的数尽量往前移
假设两个数,a,b,a在b前面而且 |ab|<k
那么a一定不可能到b的后面去
那从a向b连一条边,得到一个图
求这个图可以通过线段树来维护
做一个字典序最小的拓扑排序
a在b前面,那么在拓扑排序中,a也一定在b前面

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<set>
#define N 501000
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define lowbit(x) ((x)&(-(x)))
#define INF 214748367
using namespace std;
int n,k,a[N],t[N*10],last[N*2],next[N*2],to[N*2],tot=0,r[N],c[N],m=0;
struct cnt{
    bool operator() (int x,int y){return a[x]<a[y];}
};
multiset <int,cnt> b;
void read(int &x)
{
    char c=getchar();x=0;
    for(;c<'0'||c>'9';c=getchar());
    for(;c>='0'&&c<='9';c=getchar()) x=x*10+c-48;
}
void insert(int v,int i,int j,int x,int y)
{
    if(i==j) {t[v]=y;return;}
    int m=(i+j)/2;
    if(x<=m) insert(v+v,i,m,x,y);
    else insert(v+v+1,m+1,j,x,y);
    t[v]=214748647;
    if(t[v+v+1]>0) t[v]=min(t[v],t[v+v+1]);
    if(t[v+v]>0) t[v]=min(t[v],t[v+v]);
}
int find(int v,int i,int j,int x,int y)
{
    if(i==x&&j==y) return t[v];
    int m=(i+j)/2;
    if(y<=m) return find(v+v,i,m,x,y);
    else if(x>m) return find(v+v+1,m+1,j,x,y);
         else return min(find(v+v,i,m,x,m),find(v+v+1,m+1,j,m+1,y));
}
void put(int x,int y){next[++tot]=last[x];last[x]=tot;to[tot]=y;r[y]++;}
int main()
{
    freopen("permutation.in","r",stdin);
    freopen("permutation.out","w",stdout);
    scanf("%d%d",&n,&k);
    fo(i,1,n) read(m),a[m]=i;
    memset(t,127,sizeof(t));
    fd(i,n,1)
    {
        int x=find(1,1,n,a[i],min(a[i]+k-1,n));
        if(x>0&&x<=n) put(i,x);
        x=find(1,1,n,max(1,a[i]-k+1),a[i]);
        if(x>0&&x<=n) put(i,x);
        insert(1,1,n,a[i],i);
    }
    m=0;
    fo(i,1,n) if(r[i]==0) b.insert(i);
    while(!b.empty())
    {
        int x=*b.begin();b.erase(b.begin());
        c[++m]=a[x];
        for(int i=last[x];i;i=next[i]) {r[to[i]]--;if(r[to[i]]==0) b.insert(to[i]);}
    }
    fo(i,1,n) a[c[i]]=i;
    fo(i,1,n) printf("%d\n",a[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值