把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【AT1984】[AGC001F] Wide Swap(线段树+拓扑)

点此看题面

  • 给定一个长度为\(n\)的排列\(a_i\),可以无限次选择交换一对满足\(|a_i-a_j|=1,j-i\ge k\)\(a_i,a_j\)
  • 求能得到的字典序最小的最终排列。
  • \(n\le5\times10^5\)

逆置换

考虑令\(p_{a_i}=i\),那么就变成可以无限次选择交换一对满足\(|p_i-p_{i+1}|\ge k\)\(p_i,p_{i+1}\),要求使较小的数下标尽可能靠前。

这样一来有一个性质就很明显了,对于两个数\(x,y\)满足\(|x-y|<k\),因为它们两个之间无法发生交换,所以\(x,y\)的相对顺序无法改变。

然后再仔细一想,只要满足所有这样的\(x,y\)相对顺序不变,就必然能通过若干次操作得到对应的排列。

因此,这就是我们的唯一限制条件了。

反图拓扑排序

一个经典问题。

相对顺序无法改变显然就是拓扑关系,而要求较小的数下标尽可能小更是一个经典的拓扑排序问题,就是建出反图跑拓扑排序,把拓扑排序的队列改成优先队列,选出字典序最大的方案。

无冗余建图

注意到这里如果直接暴力建图复杂度是\(O(nk)\)的。

但实际上,以\(p_i\)向之后所有\([p_i+1,p_i+k-1]\)中的数连边为例,我们并不需要连满所有边,假设其后第一个范围内的数是\(p_x\),显然\(p_x\)一定会向之后\([p_i+1,p_i+k-1]\)中的所有点连边。

那么只要从\(p_x\)\(p_i\)连边(因为要建反图),就可以做到无冗余建图了。

具体实现中只要从后往前枚举,用值域线段树维护区间内的最小下标即可。

代码:\(O(nlogn)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 500000
#define add(x,y) (e[++ee].nxt=lnk[x],++deg[e[lnk[x]=ee].to=y])
using namespace std;
int n,k,a[N+5],p[N+5],ee,lnk[N+5],deg[N+5];struct edge {int to,nxt;}e[2*N+5];
namespace FastIO
{
	#define FS 100000
	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
	#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
	int OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
	I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
	Tp I void writeln(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);pc('\n');}
}using namespace FastIO;
class SegmentTree
{
	private:
		#define PT CI l=1,CI r=n,CI rt=1
		#define LT l,mid,rt<<1
		#define RT mid+1,r,rt<<1|1
		#define PU(x) (V[x]=min(V[x<<1],V[x<<1|1]))
		int V[N<<2];
	public:
		I void Build(PT) {if(l==r) return (void)(V[rt]=1e9);RI mid=l+r>>1;Build(LT),Build(RT),PU(rt);}//建树
		I void U(CI x,CI v,PT)//单点修改
		{
			if(l==r) return (void)(V[rt]=v);RI mid=l+r>>1;x<=mid?U(x,v,LT):U(x,v,RT),PU(rt);
		}
		I int Q(CI L,CI R,PT)//区间询问最小值
		{
			if(L<=l&&r<=R) return V[rt];RI mid=l+r>>1;return min(L<=mid?Q(L,R,LT):1e9,R>mid?Q(L,R,RT):1e9);
		}
}S;
priority_queue<int> q;I void Topo()//反图拓扑
{
	RI i,k,c=n;for(i=1;i<=n;++i) !deg[i]&&(q.push(i),0);W(!q.empty())
		for(i=lnk[k=q.top()],q.pop(),a[k]=c--;i;i=e[i].nxt) !--deg[e[i].to]&&(q.push(e[i].to),0);//每次取出最大值
	for(i=1;i<=n;++i) writeln(a[i]);//注意还原到原序列
}
int main()
{
	RI i,t;for(read(n,k),i=1;i<=n;++i) read(a[i]),p[a[i]]=i;//逆置换
	for(S.Build(),i=n;i;S.U(p[i],i),--i)//从后往前枚举
		(t=S.Q(max(p[i]-k+1,1),p[i]))<1e9&&add(p[t],p[i]),(t=S.Q(p[i],min(p[i]+k-1,n)))<1e9&&add(p[t],p[i]);//找到第一个范围内的数连边
	return Topo(),clear(),0;
}
posted @ 2021-05-17 18:45  TheLostWeak  阅读(59)  评论(0编辑  收藏  举报