[JZOJ5405]【NOIP2017提高A组模拟10.10】Permutation

Description

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

对于100% 的数据满足n <= 500000

Solution

隔着K个数之间交换比较麻烦,不如变换一下题意

原序列记录每个位置上的数什么
不妨新开一个数组Q记录每个数在哪个位置。

交换只是相邻两个交换,只要相邻两个数的差的绝对值大于等于K就可以交换。
排列P的字典序最小,与数组Q的字典序最小是等价的。

有了这个结论,就可以直接在数组Q上做了。

下面的值都是数组Q上的值
显然,如果 |xy|<K ,那么 x,y 的相对位置无论怎么换,x这个数总是在y前面的。

那么就有了一种 N2 的做法,对于每一对 |Q[i]Q[j]|<K,i<j ,把 Q[i] Q[j] 连边,构成一个DAG,那么构造出来的最小字典序的拓扑序就是最后换出来的Q,再还原成P即可。

最小字典序的拓扑序可以维护一个堆,每次取堆顶加入拓扑序,扩展节点,新走出的节点入度为0时加入堆即可。

然而可以发现,这样连边会有大量不必要的边。
对于 Q[x],Q[y],Q[z],x<y<z
如果 |Q[x]Q[y]|<K,|Q[y]Q[z]|<K,|Q[x]Q[z]|<K

那么只需要Q[x]连Q[y],Q[y]连Q[z]即可。

那么从后往前扫,对于当前扫到的Q[i],只需要找到i后面最小的x,y,使得
Q[x][Q[i]K+1,Q[i]1],Q[y][Q[i]+1,Q[i]+K1]

Q[i]分别向 Q[x],Q[y] 连边即可

这个东西可以维护一个权值线段树,从后向前扫的时候插入即可。

Code

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
#include <cstdlib>
#include <queue>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
#define N 500005
#define INF 2147483647
using namespace std;
int n,w[N],m,m1,n1,fs[N],nt[2*N],dt[2*N],rd[N],d[N],as[N],a1[N];
priority_queue <int> a;
void link(int x,int y)
{
    nt[++m1]=fs[x];
    rd[dt[fs[x]=m1]=y]++;
}
struct node
{
    int s,l,r,s1;
}tr[5*N];
void up(int k)
{
    tr[k].s=min(tr[tr[k].l].s,tr[tr[k].r].s);
    tr[k].s1=max(tr[tr[k].l].s1,tr[tr[k].r].s1);
}
void ins(int k,int l,int r,int x,int v)
{
    if(l==r) tr[k].s=v,tr[k].s1=v;
    else
    {
        int mid=(l+r)/2;
        if(!tr[k].l) tr[k].l=++n1,tr[n1].s=INF;
        if(!tr[k].r) tr[k].r=++n1,tr[n1].s=INF;
        if(x<=mid) ins(tr[k].l,l,mid,x,v);
        else ins(tr[k].r,mid+1,r,x,v);
        up(k);
    }
}
int fd(int k,int l,int r,int x,int y)
{
    x=max(x,l),y=min(y,r);
    if(x>y||k==0) return INF;
    if(l==x&&r==y) return tr[k].s;
    int mid=(l+r)/2;
    return min(fd(tr[k].l,l,mid,x,y),fd(tr[k].r,mid+1,r,x,y));
}
int fd2(int k,int l,int r,int x,int y)
{
    x=max(x,l),y=min(y,r);
    if(x>y||k==0) return 0;
    if(l==x&&r==y) return tr[k].s1;
    int mid=(l+r)/2;
    return max(fd2(tr[k].l,l,mid,x,y),fd2(tr[k].r,mid+1,r,x,y));
}
void doit()
{
    int l=0,r=d[0];
    fo(i,1,n) if(rd[i]==0) a.push(-i);
    while(r<n)
    {
        int k=-a.top();
        a.pop();
        d[++r]=k;
        for(int i=fs[k];i;i=nt[i])
        {
            int p=dt[i];
            rd[p]--;
            if(rd[p]==0) a.push(-p);
        }
    }
}
int main()
{
    cin>>n>>m;
    n1=1;
    fo(i,1,n)
    {
        scanf("%d",&a1[i]);
        w[a1[i]]=i;
    }
    ins(1,1,n,w[n],n);
    fod(i,n-1,1)
    {
        int p=fd(1,1,n,w[i],w[i]+m-1),q=fd(1,1,n,w[i]-m+1,w[i]);
        if(p!=INF) link(w[i],w[p]);
        if(q!=INF) link(w[i],w[q]);
        ins(1,1,n,w[i],i);
    }
    doit();
    fo(i,1,n) as[d[i]]=i;
    fo(i,1,n) printf("%d\n",as[i]);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值