JZOJ 5405. 【NOIP2017提高A组模拟10.10】Permutation

38 篇文章 0 订阅
30 篇文章 1 订阅

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

Data Constraint

对于前20% 的数据满足n <= 6
对于前50% 的数据满足n <= 2000
对于100% 的数据满足n <= 500000

Solution

  • 读入 p[i] ,转化为: q[p[i]]=i q[i] 表示值为 i 的位置在哪。

  • 考虑对于 q[i] q[j] ,若 |q[i]q[j]|<k (相距小于 k ),则 q[i] q[j] 始终不能互换位置。

  • 于是 q[i] q[j] (i>j) 连一条有向边即可,这样就构成了一个有向无环图。

  • 将入度为 0 的点加入一个小根堆,做一遍拓扑排序。

  • 倒着枚举 i ,每次从堆里取出一个编号最小的 x

  • 使 p[i]=x ,再将 x 连出的点入度减一,为 0 则加入堆中。

  • 最后使 q[p[i]]=i , 再将 q 输出即可。

  • 为什么要维护一个堆呢?因为如果两点连了边(不能位置互换),就不会同时存在于堆中。

  • 在队中的点,肯定是可以位置互换的,那肯定是将小的数填在位置小那里,才能保证字典序最小。

-但是这种连边时间复杂度达到了 O(N2) ,这样就超时了。

  • 由于一个点只需与离它最近的点连边,维护一个维护最小值的线段树,

  • 倒着枚举 i ,每次查询 [q[i]k+1,q[i]] [q[i],q[i]+k1] 两个区间的最小值

  • 即距离最近的两个点(一大一小),连边后再将 q[i] 这个位置单点修改成编号 i 即可。

  • 这样同样做一次拓扑排序,不会影响正确性,时间复杂度就成了 O(N log N)

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N=5e5+1;
int tot;
int a[N],b[N],f[N<<2];
int first[N],next[N<<2],en[N<<2],d[N];
bool bz[N];
priority_queue<int,vector<int>,less<int> >q;
inline int read()
{
    int X=0,w=1; char ch=0;
    while(ch<'0' || ch>'9') {if(ch=='-') w=-1;ch=getchar();}
    while(ch>='0' && ch<='9') X=(X<<3)+(X<<1)+ch-'0',ch=getchar();
    return X*w;
}
inline void insert(int x,int y)
{
    next[++tot]=first[x];
    first[x]=tot;
    en[tot]=y;
    d[y]++;
}
inline void change(int v,int l,int r,int x,int y)
{
    if(l==r)
    {
        f[v]=y;
        return;
    }
    int mid=(l+r)>>1;
    if(x<=mid) change(v<<1,l,mid,x,y); else change(v<<1|1,mid+1,r,x,y);
    f[v]=min(f[v<<1],f[v<<1|1]);
}
inline int find(int v,int l,int r,int x,int y)
{
    if(x<=l && r<=y) return f[v];
    int mid=(l+r)>>1;
    if(y<=mid) return find(v<<1,l,mid,x,y);
    if(x>mid) return find(v<<1|1,mid+1,r,x,y);
    return min(find(v<<1,l,mid,x,mid),find(v<<1|1,mid+1,r,mid+1,y));
}
int main()
{
    int n=read(),k=read();
    for(int i=1;i<=n;i++) b[a[i]=read()]=i;
    memset(f,60,sizeof(f));
    for(int i=n;i;i--)
    {
        int x=find(1,1,n,b[i]-k+1,b[i]);
        if(x<=n) insert(b[x],b[i]);
        x=find(1,1,n,b[i],b[i]+k-1);
        if(x<=n) insert(b[x],b[i]);
        change(1,1,n,b[i],i);
    }
    for(int i=1;i<=n;i++)
        if(!d[i]) q.push(i);
    for(int i=n;i;i--)
    {
        a[i]=q.top();
        q.pop();
        for(int j=first[a[i]];j;j=next[j])
            if(!--d[en[j]]) q.push(en[j]);
    }
    for(int i=1;i<=n;i++) b[a[i]]=i; 
    for(int i=1;i<=n;i++) printf("%d\n",b[i]);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值