其他分享
首页 > 其他分享> > [CSP-S2019] 树上的数 树上推理

[CSP-S2019] 树上的数 树上推理

作者:互联网

还没整完明天再说,靠不小心点了发布、、

Link

某些废话

devinwang勒令我们补掉CSP2019的题 /youl
看了半天题解脑子里还是浆糊,退役人是我这样的。
这篇题解写的非常清楚,我写这个只是给我自己看。

题意

现在给你一棵树,数字 \(i\) 在编号为 \(p_i\) 的节点上。
每次删除一条边,删边时交换两节点上的数字。
定义数组 \(ans\),\(ans_i\) 是删除所有边时,数字 \(i\) 所在的节点编号。
求字典序最小的 \(ans\)。
\(n\le 2000\)

题解

很显然,我们从小到大考虑每个数字,尽可能的把它填在编号最小的节点上。
考虑部分分。

菊花图

假设我们初始是这样一张图。(图中节点上写的数字为原始节点编号,边上是边的编号)

如果依次删去 1,2,3,4,5 这些边,最终的图将会是:

如果我们将每个节点上初始和最终的标号相连,那么就会获得

这是个环。
(实际上你无论哪种情况最后形成的不都是若干个环……只不过菊花图只有一个……)

这样我们使用并查集维护联通性就可以获得菊花图的部分分。
代码中 \(i\) 为当前数字,\(x\) 是初始节点,\(j\) 是目标节点。显然只能在连最后一条边的时候出现环。

for(int i = 1; i <= n; i++) {
	int x = p[i];
	for(int j = 1; j <= n; j++) {
		if(!vis[j] && (i == n || find(x) != find(j))) {
			printf("%d ", j);
			fa[find(x)] = find(j);
			vis[j] = 1;
			break;
		}
	}
}


我们考虑让节点2上的数字,移动到节点4上去,那么我们就有限制

那么就可以定出这些边之间的限制,如果违背了之前定下的限制,那肯定不可行。比如我不能先要求节点2的数字移动到节点4上去,然后又要求节点1上的数字移动到节点3上去。
具体可以用0,1,2表示没有限制,左<右,左>右表示一个点两侧边的限制,具体实现我没写。

正解

我们从小到大考虑这些数字,贪心的选择最小的可行的目标节点,然后更新信息。

链的部分分启发了我们考虑边和边之间的限制。
如果将节点 \(u\) 上的数字移动到节点 \(v\),那么一定满足

那么我们考虑怎么用并查集维护这个东西。
对于每个节点 \(u\),我们开一个虚点 \(u\)。对于每个虚点 \(u\) 开一个并查集数组 \(fa\)

那么哪些情况是不可行的呢?

钦定in和out挺好搞的,然后我们发现第二个很难维护。
其实只要稍微改一下就行了。

最后发现:本来应当对每个点开一个并查集,但实际上每个点所对应的虚点+每个点连出的边,显然是不相同的,所以直接开N*3大小的并查集即可。

代码

code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define mkp make_pair
#define pb push_back
#define PII pair<int, int>
#define PLL pair<ll, ll>
#define ls(x) ((x) << 1)
#define rs(x) ((x) << 1 | 1)
#define fi first
#define se second
const int N = 2010, M = N * 3;
int n, mn, p[N], deg[N], ans[N];
int in[M], ot[M];
int e, hd[N], to[M], nxt[M];
int fa[M], sz[M];
void clear() {
	for(int i = 1; i <= e; i++)
		fa[i] = i, sz[i] = 1, in[i] = ot[i] = 0;
}
int find(int x) {
	return (x == fa[x]) ? x : fa[x] = find(fa[x]);
}
void merge(int x, int y) {
	int fx = find(x), fy = find(y);
//	if(fx == fy) return;已经规避了相等的情况。 
	fa[fx] = fy; sz[fy] += sz[fx]; 
	ot[x] = in[y] = 1;
	return;
}
void add(int u, int v) {
	to[++e] = v; nxt[e] = hd[u]; hd[u] = e;
}
void init() {
	for(int i = 1; i <= n; i++)
		hd[i] = deg[i] = 0;
	for(int i = 1; i <= e; i++)
		to[i] = nxt[i] = 0;
	e = (n + 1) / 2 * 2 + 1;
	return;
}
bool check(int x, int y, int len) {
	if(in[y] || ot[x]) return 0;
	x = find(x); y = find(y);
	if(x == y && sz[x] != len) return 0;
	return 1;
}
void dfs(int u, int ed) {
	if(ed != u && check(ed, u, deg[u] + 1))
		mn = min(mn, u);
	for(int i = hd[u]; i; i = nxt[i]) {
		int v = to[i]; if(i == ed) continue;
		if(check(ed, i, deg[u] + 1))
			dfs(v, i ^ 1);
	}
	return;
}
bool dfs2(int u, int ed, int goal) {
	if(u == goal) return merge(ed, u), 1;
	for(int i = hd[u]; i; i = nxt[i]) {
		int v = to[i]; if(i == ed) continue;
		if(dfs2(v, i ^ 1, goal))
			return merge(ed, i), 1;
	}
	return 0;
}
void solve() {
	for(int i = 1; i <= n; i++) {
		mn = n + 1;
		dfs(p[i], p[i]); dfs2(p[i], p[i], mn);
		ans[i] = mn;
	}
	for(int i = 1; i <= n; i++)
		printf("%d ", ans[i]);
	puts("");
	return;
}
int main(){
	int T; scanf("%d", &T);
	while(T--) {
		scanf("%d", &n);
		init();
		for(int i = 1; i <= n; i++)
			scanf("%d", &p[i]);
		for(int i = 1, u, v; i < n; i++) {
			scanf("%d%d", &u, &v);
			add(u, v); add(v, u);
			deg[u]++; deg[v]++;
		}
		clear(); solve();	
	} 
	return 0;
}

标签:边中,查集,节点,edge,虚点,S2019,树上,CSP,号边
来源: https://www.cnblogs.com/zdsrs060330/p/15521850.html