【AtCoder Grand Contest 001 F】【JZOJ 5405】 Permutation

Description

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

Analysis

直接做P不知能不能做,但是另一个思路是弄出Q[P[i]]=i,然后依然要求Q字典序尽量小
转化模型:我们每次可以交换Q中相邻两个数,当且仅当他们的差>=K
也就是说,对于Q[i],Q[j],如果|Q[i]-Q[j]|< K,i和j的相对位置永远不可能改变
那么序列中最多有O(N^2)个确定的相对关系,尝试连边,发现会构成一个DAG
那么Q最小的字典序就是DAG上字典序最小的拓扑序,可以做到O(N^2)
但是,其实可以发现连的边有许多是无用的,仔细分析发现每个位置i最多向后连出两条边,即大于Q[i]的最靠左的,和小于Q[i]最靠左的。
这样边数变成O(n),复杂度为寻找相对关系的复杂度,可以使用数据结构做到O(nlogn)

Code

#include<set>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,b,a) for(int i=b;i>=a;i--)
#define efo(i,v,u) for(int i=last[v],u=to[i];i;i=next[i],u=to[i])
#define mset(a,x) memset(a,x,sizeof(a))
using namespace std;
typedef long long ll;
char ch;
void read(int &n)
{
    n=0;
    for(ch=getchar();ch<'0' || ch>'9';ch=getchar());
    for(;'0'<=ch && ch<='9';ch=getchar()) n=n*10+ch-'0';
}
const int N=5e5+5,M=2e6+5,INF=2139062143;
int n,m,a[N],b[N],final[N],ans[N],in[N],tr[N<<2];
int tot,to[M],next[M],last[N];
void link(int u,int v){in[v]++,to[++tot]=v,next[tot]=last[u],last[u]=tot;}
struct node
{
    int x;
    node(int x0){x=x0;}
    friend bool operator <(node n1,node n2)
    {
        return b[n1.x]<b[n2.x];
    }
};
set<node> s;
void add(int v,int l,int r,int x,int y)
{
    if(l==r)
    {
        tr[v]=y;
        return;
    }
    int mid=(l+r)>>1;
    if(x<=mid) add(v+v,l,mid,x,y);
    else add(v+v+1,mid+1,r,x,y);
    tr[v]=min(tr[v+v],tr[v+v+1]);
}
int query(int v,int l,int r,int x,int y)
{
    if(x>y) return INF;
    if(l==x && r==y) return tr[v];
    int mid=(l+r)>>1;
    if(y<=mid) return query(v+v,l,mid,x,y);
    else
    if(x>mid) return query(v+v+1,mid+1,r,x,y);
    else
    return min(query(v+v,l,mid,x,mid),query(v+v+1,mid+1,r,mid+1,y));
}
int main()
{
    freopen("permutation.in","r",stdin);
    freopen("permutation.out","w",stdout);
    int n,m;
    read(n),read(m);
    fo(i,1,n) read(a[i]),b[a[i]]=i;
    mset(tr,127);
    fd(i,n,1)
    {
        int upper=INF,lower=INF;
        if(b[i]<n) upper=query(1,1,n,b[i]+1,min(n,b[i]+m-1));
        if(b[i]>1) lower=query(1,1,n,max(1,b[i]-m+1),b[i]-1);
        if(upper<=n) link(i,upper);
        if(lower<=n) link(i,lower);
        add(1,1,n,b[i],i);
    }
    fo(i,1,n)
        if(!in[i]) s.insert(node(i));
    while(!s.empty())
    {
        int x=(*s.begin()).x;s.erase(s.begin());
        final[++final[0]]=b[x];
        efo(i,x,y)
            if(!--in[y]) s.insert(y);
    }
    fo(i,1,n) ans[final[i]]=i;
    fo(i,1,n) printf("%d\n",ans[i]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值