[??记录]AT1984 [AGC001F] Wide Swap

command_block

2021-04-22 07:50:49

Personal

**题意** : 给出一个长度为 $n$ 的排列 $p$ 。 当 $i,j$ 满足 $|i-j|\geq k$ 且 $|p_i-p_j|=1$ 时,可以交换 $p_i,p_j$。 求通过若干次上述操作能得到的字典序最小的排列。 $n\leq 5\times10^5$ ,时限$\texttt{5s}$。 ------------ 考虑 $p$ 的逆排列 $q$ ,则交换条件变为 : $|q_i-q_j|\geq k$ 且 $i,j$ 相邻即可交换 $q_i,q_j$。 对于一对 $q_i,q_j$ ,若 $|q_i-q_j|<k$ ,则两者的相对顺序是固定的,否则可以任意变动。 于是,若 $q_i$ 一定要在 $q_j$ 前面,则连边 $i\rightarrow j$。 这张有向图的每种拓扑序都是一个能够造出的排列。 逆排列和原排列的字典序关系比较复杂,我们不妨直接在原排列上建图。 对于一对 $p_i,p_j$ ,若 $|i-j|<k$ ,则要求 $p_i,p_j$ 的大小关系不变。 若要求 $p_i<p_j$ ,则连边 $i\rightarrow j$。 这张有向图的每种拓扑序都是一个能够造出的排列。现在要使得 $p_1$ 尽量小,在此基础上使得 $p_2$ 尽量小…… - $\text{sub ploblem}$ : [P3243 [HNOI2015]菜肴制作](https://www.luogu.com.cn/problem/P3243) 兔队告诉我们,直接在拓扑排序的同时贪心取编号最小的点扩展是错的,应该使用下面的算法 : 建立反图,在反图上拓扑排序,并从 $N$ 到 $1$ 编号,每次贪心取最大的编号扩展。 该算法的正确性依赖于“**字典拓扑原理**” : > 感谢 yhx 的指导 - 对于任意一个 ${\bf DAG}\ G$ 和 $G$ 的一个拓扑序 $p$ : $p$ 是字典序最大的,当且仅当 $p^{-1}$ 是字典序最大的。 (若将“大”改为“小”,则命题不一定成立) 在第一个算法中,我们每次贪心地取编号最小的点,即使得 $p^{-1}$ 的字典序最小,但 $p$ 的字典序不一定最小。 在第二个算法中,我们将图的边集翻转,问题变为求出字典序最大的 $p$ ,于是等效于求出字典序最大的 $p^{-1}$ ,则可以贪心。 本题中, DAG 的边数达到 $O(nk)$ ,不能直接暴力建边。 点 $i$ 连向的点的集合为 $\{j:|i-j|<k,p_j<p_i\}$ ,故点 $i$ 入度为 $0$ 的充要条件是在 $[i-k,i+k]$ 内 $p_i$ 为最大值。 用大根堆维护目前入度为 $0$ 的点的集合。 使用线段树维护区间最大值,当删除点 $i$ 时,查询 $[i-k,i),(i,i+k]$ 中的最大值编号,并查看这两个编号是否入度变为 $0$。 复杂度 $O(n\log n)$。 ```cpp #include<algorithm> #include<cstdio> #include<queue> #define Pr pair<int,int> #define fir first #define sec second #define mp make_pair #define MaxN 500500 using namespace std; Pr a[MaxN<<2]; inline void up(int u) {a[u]=max(a[u<<1],a[u<<1|1]);} int n,x[MaxN]; void build(int l=1,int r=n,int u=1) { if (l==r){a[u]=mp(x[l],l);return ;} int mid=(l+r)>>1; build(l,mid,u<<1); build(mid+1,r,u<<1|1); up(u); } int to; void del(int l=1,int r=n,int u=1) { if (l==r){a[u]=mp(0,0);return ;} int mid=(l+r)>>1; if (to<=mid)del(l,mid,u<<1); else del(mid+1,r,u<<1|1); up(u); } int wfl,wfr;Pr ret; void qry(int l=1,int r=n,int u=1) { if (wfl<=l&&r<=wfr){ret=max(ret,a[u]);return ;} int mid=(l+r)>>1; if (wfl<=mid)qry(l,mid,u<<1); if (mid<wfr)qry(mid+1,r,u<<1|1); } priority_queue<int> q; int k,ans[MaxN]; int main() { scanf("%d%d",&n,&k); for (int i=1;i<=n;i++) scanf("%d",&x[i]); build(); for (int i=1;i<=n;i++){ wfl=max(1,i-k+1);wfr=min(n,i+k-1); ret=mp(0,0);qry(); if (ret.sec==i)q.push(i); } int tim=n; while(!q.empty()){ int u=q.top();q.pop(); ans[u]=tim--; to=u;del(); wfl=max(1,u-k+1);wfr=u-1; if (wfl<=wfr){ ret=mp(0,0);qry(); int i=ret.sec; if (i){ int i=ret.sec; wfl=max(1,i-k+1);wfr=min(n,i+k-1); ret=mp(0,0);qry(); if (ret.sec==i)q.push(i); } } wfl=u+1;wfr=min(n,u+k-1); if (wfl<=wfr){ ret=mp(0,0);qry(); int i=ret.sec; if (i){ wfl=max(1,i-k+1);wfr=min(n,i+k-1); ret=mp(0,0);qry(); if (ret.sec==i)q.push(i); } } } for (int i=1;i<=n;i++) printf("%d\n",ans[i]); return 0; } ```