1. 程式人生 > 其它 >AtCoder Grand Contest 001 F - Wide Swap

AtCoder Grand Contest 001 F - Wide Swap

\(\text{Problem}\)

給定一個長度為 \(N\) 的序列 \(P\) 和一個數 \(K\)
對於滿足 \(j-i \ge K and |P_i-P_j|=1\) 一對 \(i,j\) 可交換 \(P_i,P_j\)
求若干次交換後字典序最小的 \(P\)

\(\text{Solution}\)

一道極好的思維題
第一步就想不到。。。
考慮 \(P\) 的逆排列 \(Q\),即令 \(Q[P[i]]=i\)
則發現 \(P\) 中的交換條件在 \(Q\) 中變為相鄰的兩個數,當數值差大於等於 \(K\) 時即可交換
\(Q\) 中的數值表示 \(P\) 中的位置,\(Q\) 數值對應的下標表示 \(P\)

這個位置填的數
考慮 \(Q\) 中一個數往左交換,即 \(P\) 這個位置往小填數
當在 \(Q\) 中這個數遇到一個與其差小於 \(K\) 的數時,發現無論怎麼換也不能換到那個數左邊
即在 \(P\) 中這個位置填的數必然大於 \(Q\) 那個數對應到 \(P\) 的位置所填的數
對於 \(Q\) 中的每一個數,往左的與其差小於 \(K\) 的數都如此
那麼就可以建個 \(DAG\),表示 \(P\) 位置上填的數的限制關係
回到本題,我們希望位置越小的填的數越小
怎麼利用這張圖構造最小序列?
一個顯然正確的做法是做出原圖的反圖,每次取出入度為 \(0\) 的最大的編號填上最大的數

好,我們終於完成了解題的思路
遺憾地發現這個做法是 \(O(NK)\)


發現 \(DAG\) 中很多邊的限制是等效的
根據連邊的規則,對於 \(x\),其連向點值域為 \([x-K+1,x]\)\([x,x+K-1]\)
發現 \([x-K+1,x]\) 中的點兩兩總有邊,\([x,x+K-1]\) 也一樣,且總是 \(Q\) 中位置靠後的連向位置靠前的
那對於一個 \(x\),我們只需在 \([x-K+1,x]\)\([x,x+K-1]\) 中各找一個最靠後的點連上即可
這樣就完成了 \(DAG\) 邊的去冗

\(\text{Code}\)

#include <cstdio>
#include <iostream>
#include <queue>
#define ls (p << 1)
#define rs (ls | 1)
#define IN inline
#define RE register
using namespace std;
 
const int N = 5e5 + 5;
int n, k, p[N], q[N], a[N], seg[N << 4], deg[N], h[N], tot;
priority_queue<int> Q;
struct edge{int to, nxt;}e[N << 1];
IN void add(int x, int y){e[++tot] = edge{y, h[x]}, h[x] = tot;}
 
IN void read(int &x)
{
	x = 0; char ch = getchar();
	for(; !isdigit(ch); ch = getchar());
	for(; isdigit(ch); x = (x<<3)+(x<<1)+(ch^48), ch = getchar());
}
 
void Insert(int p, int l, int r, int x, int v)
{
	if (l == r) return seg[p] = v, void();
	int mid = l + r >> 1;
	if (x <= mid) Insert(ls, l, mid, x, v);
	else Insert(rs, mid + 1, r, x, v);
	seg[p] = max(seg[ls], seg[rs]);
}
int Query(int p, int l, int r, int x, int y)
{
	int mid = l + r >> 1;
	if (x <= l && r <= y) return seg[p];
	int res = 0;
	if (x <= mid) res = Query(ls, l, mid, x, y);
	if (y > mid) res = max(res, Query(rs, mid + 1, r, x, y));
	return res;
}
 
IN void TopSort()
{
	for(RE int i = 1; i <= n; i++) if (!deg[i]) Q.push(i);
	int cnt = n;
	while (!Q.empty())
	{
		int x = Q.top(); Q.pop(), a[x] = cnt--;
		for(RE int i = h[x]; i; i = e[i].nxt)
		if (!(--deg[e[i].to])) Q.push(e[i].to);
	}
}
 
int main()
{
	read(n), read(k);
	for(RE int i = 1; i <= n; i++) read(p[i]), q[p[i]] = i;
	for(RE int i = 1; i <= n; i++)
	{
		int x = Query(1, 1, n, q[i], min(n, q[i] + k - 1));
		if (x) add(q[i], q[x]), ++deg[q[x]];
		x = Query(1, 1, n, max(1, q[i] - k + 1), q[i]);
		if (x) add(q[i], q[x]), ++deg[q[x]];
		Insert(1, 1, n, q[i], i);
	}
	TopSort();
	for(RE int i = 1; i <= n; i++) printf("%d\n", a[i]);
}