JZOJ 5405.【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
Data Constraint
对于100% 的数据满足n <= 500000

题解

在比赛的过程中,我发现p[i]变成x,当前仅当条件:x和p[i]之间的所有数,p[i]都与他们距离≥k。
为了方便解决此题,我们将问题转化为:q[p[i]]=i,然后对q序列解决问题。那么两个数能移动,当且仅当相邻且 |q[i]q[i1]|k .
所以,我们只要找到与i相关的两个数j1,j2>i,
j1[q[i]k+1,q[i]]j2[q[i],q[i]+k1] 。然后q[i]向q[j1],q[j2]各连一条边,
表示j1无论怎么只靠跟后面换,都不能够换到i前面(若j1可以跟前面换则不优)。j2同理
即表示,最后i表示的数在j1,j2前面。

这样子我们搞出了一个DAG。
由于题目要求输出最小字典序的序列,所以保证拓扑序字典序最小即可。
解决方案:用优先队列存储即可,每次让最小的出队就行了。
然而C++的priority_queue默认为“大根堆”。“小根堆”怎么办?
解决方案:将加进优先队列的数取相反数,然后取出来的时候再去相反数。
对这道题目的心得:
①不要将数列上的数看得太死,要让他们在脑中”动起来”。
②一道题目这么难,肯定有突破口,比如这题的一定交换相邻的两个数,这两个数绝对值等于1!!!所以可以想到一直交换,直到有一个交换不到的数就截止。
③最重要的一点,对于这种序列(这种序列是排列)的问题,如果涉及到要利用”每个数的位置”来解决问题,就可以试下考虑逆数组。即q[p[i]]=i。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define N 500010
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
struct note{
    int to,next;
};note edge[N*2];
int p[N],q[N];
int tr[N*20],ru[N];
int i,j,k,L,R,l,r,n,temp,now,ans,step;
int tot,head[N];
priority_queue<int>qu;
bool pp;
void read(int &n){
    n=0;char ch=getchar();
    while((ch<'0' || ch>'9') && ch!='-')ch=getchar();
    int w=1;
    if(ch=='-')w=-1,ch=getchar();
    while('0'<=ch && ch<='9')n=n*10+ch-'0',ch=getchar();
    n*=w;
}
void write(int x){
    if(x>9)write(x/10);
    putchar(x%10+'0');
}
void lb(int x,int y){
    edge[++tot].to=y;
    edge[tot].next=head[x];
    head[x]=tot;
    ru[y]++;
}
void update(int ps){
    tr[ps]=min(tr[ps*2],tr[ps*2+1]);
}
void change(int ps,int l,int r,int x,int z){
    if(l==r){
        tr[ps]=z;
        return;
    }
    int wz=(l+r)/2;
    if(x<=wz)change(ps*2,l,wz,x,z);
        else change(ps*2+1,wz+1,r,x,z);
    update(ps);
}
void find(int ps,int l,int r,int x,int y){
    if(l==x && r==y){
        temp=min(temp,tr[ps]);
        return;
    }
    int wz=(l+r)/2;
    if(y<=wz)find(ps*2,l,wz,x,y);else
    if(x>wz)find(ps*2+1,wz+1,r,x,y);else{
        find(ps*2,l,wz,x,wz);
        find(ps*2+1,wz+1,r,wz+1,y);
    } 
}
int main(){
    freopen("permutation.in","r",stdin);
    freopen("permutation.out","w",stdout);
    read(n),read(k);
    fo(i,1,n)read(p[i]),q[p[i]]=i;
    memset(tr,127,sizeof(tr));
    fd(i,n,1){
        L=max(1,q[i]-k+1);R=q[i];temp=n+1;
        find(1,1,n,L,R);
        if(temp<=n)lb(q[i],q[temp]);
        L=q[i];R=min(n,q[i]+k-1);temp=n+1;
        find(1,1,n,L,R);
        if(temp<=n)lb(q[i],q[temp]);
        change(1,1,n,q[i],i);
    }
    fo(i,1,n)if(!ru[i]) qu.push(-i);
    memset(q,0,sizeof(q));
    fo(i,1,n){
        int now=qu.top();
        now=-now;
        q[i]=now;
        qu.pop();
        for(j=head[now];j;j=edge[j].next){
            ru[edge[j].to]--;
            if(!ru[edge[j].to])qu.push(-edge[j].to);
        }
    }
    fo(i,1,n)p[q[i]]=i;
    fo(i,1,n)write(p[i]),putchar('\n');
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值