8.30 广泛交换意见

题意

给一个长度为\(N\)的排列\(P\)与一个正整数\(K\),可以进行如下操作:

对于两个满足\(|i-j|\geq K\)\(|P_i-P_j|=1\)的下标\(i\)\(j\),交换\(P_i\)\(P_j\)

我们的目的是要求操作后的排列字典序最小


解法

首先,这两个条件都不好判断,直接做是不太好搞的

考虑把这个数组映射一下(这好像是对于排列的一个常见套路?)

\(pos[P_i]=i\),即元素\(P_i\)的位置为\(i\)

我们对\(pos\)数组进行操作,操作就转化为了:

可以交换两个相邻元素,并且相邻元素的差的绝对值\(\geq K\)

我们发现,对于两个元素\(i,j,i<j\),如果\(|i-j|<K\),那么它们的相对位置永远不会改变

也就是说无论进行多少次交换,如何交换,\(i\)一定会在\(j\)之前

这就启发我们用拓扑排序进行求解

但是我们应按照一种什么样的规则选择求出的拓扑序,使得原排列的字典序最小呢?

网上有许多题解都认为,原序列的字典序最小等价于求逆序列的字典序最小

但实际上这个想法是错误的,可以举一组反例来说明

  • \(rev\{2.3.1\} = \{3.1.2\}\)

  • \(rev\{3.1.2\} = \{2.3.1\}\)

我们可以发现,第一个排列的字典序比第二个大,但其逆序列的字典序却比第二个小

实际上,真正正确的结论是原序列的字典序最小,等价于其逆序列的反序列的字典序最大

那么接下来的任务就是建图了

为了避免重复连边,我们规定一个元素仅能向其后面的元素连边

考虑连边的意义,若存在一条有向边\(E(u,v)\),说明\(u,v\)之间的相对顺序已经确定即\(v\)\(u\)之后

对于一个元素\(x\),它会向值域为\((x-K,x)\cup (x,x+K)\)的元素连边,并且要求这些元素的初始位置在\(x\)之后

但是这样暴力连边,边的数量是\(O(N^2)\)级别的

我们可以发现,相对顺序是有传递关系的,比如说对于三条边\(A \rightarrow B, B\rightarrow C,A\rightarrow C\),第三条边完全可以被前两条边替代,那么它就是无效的

为了去掉这些无效的边,我们规定\(x\)只向值域为\((x-K,x)\)\((x,x+K)\)中的位置在\(x\)之后且最小的元素连边

这是因为我们可以发现由于这两个集合的大小均不超过\(K\),那么它们内部一定是互有连边的

那么我们只需要连一个位置最靠前的元素,就可以根据这个元素扩张到整个集合了

由于是求逆序列的反序列最大,我们需要建反图,并用大根堆求字典序最大的序列

找到这个元素可以用线段树实现,由于合法的元素一定在当前元素之后,所以倒序加点即可


代码

#include <queue>
#include <cstdio>
#include <cctype>
#include <cstring>

using namespace std;

const int N = 5e5 + 10;

int read();

int n, k;
int p[N], q[N];

int cap;
int head[N], deg[N], to[N << 1], nxt[N << 1];

priority_queue<int> que;

inline int min(int x, int y) {
	return x < y ? x : y;	
}

inline int max(int x, int y) {
	return x > y ? x : y;	
}

struct SegTree {
	
	int val[N << 2];
	
	void init() {
		memset(val, 0x3f, sizeof val); 
	}
	void change(int x, int l, int r, int k, int v) {
		if (l == r)	return val[x] = v, void();
		int mid = l + r >> 1;
		if (k <= mid)	
			change(x << 1, l, mid, k, v);
		else
			change(x << 1 | 1, mid + 1, r, k, v);
		val[x] = min(val[x << 1],  val[x << 1 | 1]); 
	}
	int query(int x, int l, int r, int ls, int rs) {
		if (ls <= l && r <= rs)	return val[x];
		int mid = l + r >> 1, res = 0x3f3f3f3f;
		if (ls <= mid)	
			res = min(res, query(x << 1, l, mid, ls, rs));
		if (rs > mid)
			res = min(res, query(x << 1 | 1, mid + 1, r, ls, rs));
		return res;	
	}
} tr;

inline void add(int x, int y) {
	to[++cap] = y, nxt[cap] = head[x], head[x] = cap;	
	++deg[y];
}

int main() {
	
	n = read(), k = read();
	for (int i = 1; i <= n; ++i)	p[i] = read();
	for (int i = 1; i <= n; ++i)	q[p[i]] = i;
	
	tr.init();
	for (int i = n; i >= 1; --i) {
		int x = tr.query(1, 1, n, q[i], min(q[i] + k - 1, n));
		if (x <= n)
			add(q[x], q[i]);
		int y = tr.query(1, 1, n, max(q[i] - k + 1, 1), q[i]);
		if (y <= n)
			add(q[y], q[i]);
		tr.change(1, 1, n, q[i], i);
	}
	
	for (int i = 1; i <= n; ++i)
		if (!deg[i])	que.push(i);
	for (int i = n; i >= 1; --i) {
		int u = que.top(); que.pop();
		p[i] = u;
		for (int i = head[u]; i; i = nxt[i])
			if (!--deg[to[i]])	que.push(to[i]);
	}
	
	for (int i = 1; i <= n; ++i)	q[p[i]] = i;
	for (int i = 1; i <= n; ++i)	printf("%d\n", q[i]);
	
	return 0;
}

int read() {
	int x = 0, c = getchar();
	while (!isdigit(c))	c = getchar();
	while (isdigit(c))	x = x * 10 + c - 48, c = getchar();
	return x;	
}
posted @ 2019-08-30 22:33  四季夏目天下第一  阅读(139)  评论(0编辑  收藏  举报