「CSP-S 2019」树上的数(树上推理)

Posted coldchair

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了「CSP-S 2019」树上的数(树上推理)相关的知识,希望对你有一定的参考价值。

https://loj.ac/problem/3210

过了这么久看看自己要多久才能切这题,发现还是想歪了一次。

先考虑暴力的做法。

还是贪心的逐位确定,逐位确定判有没有解,相当于下面的问题:
树上有一些路径,一条路径表示要把(x)的数字换到(y)去,问有没有解。

对于一条路径(p[1],p[2],…,p[m]),限制如下:

1.((p[1],p[2]))(p[1])的所有相邻边中时间最小的。
2.((p[[m-1],p[m]))(p[m])的所有相邻边中时间最大的。
3.(forall iin [1,m-2],(p[i],p[i+1])和(p[i+1],p[i+2]))(p[i+1])的所有相邻边中时间是相邻的(前小于后)。

发现所有限制都是对于一个点的相邻边的,因为是树,所以不同点之间的相临边限制不会影响。

那么只看每个点的相邻边是否有满足条件的解。

暴力的做法就是先把3限制的缩成若干段段(段内要合法),然后若(T(i)<T(j)),则(i->j)连边,看有没有环就好了。

仔细思考,除了3限制就只有最小最大限制,那么可以总结为以下几个限制:
1.每一段是合法的(不能有反向边、跨越边)
2.min前面不能有小于它的
3.max后面不能有大于它的
4.min、max若处于一段,则这一段的长度必须是中转点的度数

复杂度没怎么变,一共要check(O(n^2))次,每次(O(n))

考虑对于每一位,不去枚举它选什么,而是先求它能选什么,再从中选最小的。

假设第(?)位的起点是(x),以它为根,dfs一遍,处理上面四条限制,就可以求出每个点能不能作为终点了!

需要并查集维护段,时间复杂度:(O(T*n^2α))

Code:

#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define fo(i, x, y) for(int i = x, _b = y; i <= _b; i ++)
#define ff(i, x, y) for(int i = x, _b = y; i <  _b; i ++)
#define fd(i, x, y) for(int i = x, _b = y; i >= _b; i --)
#define ll long long
#define pp printf
#define hh pp("
")
using namespace std;

const int N = 4005;

int T;
int n, x, y, a[N], ia[N];

int fi[N], nt[N], to[N], tot;

void link(int x, int y) {
	nt[++ tot] = fi[x], to[tot] = y, fi[x] = tot;
}

void cl() {
	fo(i, 1, n) fi[i] = 0;
	tot = 1;
}

int m;

int us[N], b[N];

int l[N], r[N], f[N], siz[N], du[N];

int F(int x) {
	return f[x] == x ? x : (f[x] = F(f[x]));
}

void bin(int x, int y) {
	if(F(x) != F(y)) {
		x = f[x], y = f[y];
		siz[y] += siz[x];
		f[x] = y;
	}
}

int pd(int x, int y) {
	return F(x) == F(y);
}

int mi[N], mx[N];

int rt, fa[N], fq[N], ok[N], ok_ed[N];

void dg(int x) {
	if(x != rt) {
		if(fa[x] == rt) {
			int y = fa[x], u = fq[x];
			if(ok[x])
			if(mi[y] && mi[y] != u) ok[x] = 0; else
			if(l[u]) ok[x] = 0; else
			if(pd(u, mx[y]) && siz[F(u)] != du[y]) ok[x] = 0;
		} else {
			int y = fa[x];
			int u = fq[y] ^ 1, v = fq[x];
			if(ok[x])
			if(r[u] && r[u] != v) ok[x] = 0; else
			if(l[v] && l[v] != u) ok[x] = 0; else
			if(pd(u, v) && r[u] != v) ok[x] = 0; else
			if(pd(mx[y], u)) ok[x] = 0; else
			if(pd(mi[y], v)) ok[x] = 0; else
			if(pd(mi[y], u) && pd(mx[y], v) && !pd(u, v) && siz[F(u)] + siz[F(v)] != du[y]) ok[x] = 0;
		}
		int u = fq[x] ^ 1;
		ok_ed[x] = 1;
		if(mx[x] && mx[x] != u) ok_ed[x] = 0; else
		if(r[u]) ok_ed[x] = 0; else
		if(pd(u, mi[x]) && siz[F(u)] != du[x]) ok_ed[x] = 0;
	}
	for(int i = fi[x]; i; i = nt[i]) {
		int y = to[i]; if(y == fa[x]) continue;
		fa[y] = x;
		fq[y] = i;
		ok[y] = ok[x];
		dg(y);
	}
}

void gao(int x, int y) {
	mx[y] = fq[y] ^ 1;
	for(; fa[y] != x; y = fa[y]) {
		int u = fq[fa[y]] ^ 1, v = fq[y];
		r[u] = v; l[v] = u;
		bin(u, v);
	}
	mi[x] = fq[y];
}

void work() {
	cl();
	scanf("%d", &n);
	fo(i, 1, n) {
		scanf("%d", &x);
		a[x] = i; ia[i] = x;
	}
	fo(i, 1, n) du[i] = 0;
	fo(i, 1, n - 1) {
		scanf("%d %d", &x, &y);
		link(x, y); link(y, x);
		du[x] ++, du[y] ++;
	}
	fo(i, 1, tot) {
		f[i] = i; siz[i] = 1;
		l[i] = r[i] = mi[i] = mx[i] = 0;
	}
	fo(i, 1, n) us[i] = 0;
	for(m = 1; m <= n; m ++) {
		rt = ia[m];
		ok[rt] = 1;
		fo(i, 1, n) fa[i] = 0;
		dg(rt);
		fo(i, 1, n) if(rt != i && !us[i] && ok[i] && ok_ed[i]) {
			us[i] = 1;
			b[m] = i;
			gao(rt, i);
			break;
		}
	}
	if(n == 1) b[1] = 1;
	fo(i, 1, n) pp("%d ", b[i]); hh;
}

int main() {
	freopen("tree.in", "r", stdin);
	freopen("tree.out", "w", stdout);
	scanf("%d", &T);
	fo(ii, 1, T) {
		work();
	}
}

以上是关于「CSP-S 2019」树上的数(树上推理)的主要内容,如果未能解决你的问题,请参考以下文章

JZOJ6431luoguP5658CSP-S2019括号树

[CSP-S模拟测试]:树(树上LIS+主席树+线段树)

CSP2019 树上的数 题解

[CSP-S模拟测试]:Park(树上DP)

csp-s模拟测试50(9.22)「施工(单调栈优化DP)」·「蔬菜(二维莫队???)」·「联盟(树上直径)」

暑假考试题6:single 单(树上推理)