20211026 Path

虚树真是个好东西

Description

给一个有 \(n\) 个点的树和一个 \(q\)

之后有 \(q\) 次询问,每次给定一个 \(v\)\(k\) ,然后给 \(k\) 个数 \(x_i\) ,求在这个树上,在加的边的数量不超过 \(v\) 的情况下,能否找到一条简单路径,刚好按照顺序经过了给定的 \(k\) 个点。

\(n \leq 10 ^ 5\ ,\ q \leq 2\cdot 10 ^ 4\ ,\ k\leq 20\ ,\ v\leq 4\)

Analysis

第一眼看上去还想非常不可做。。

但是注意到 \(k\) 只有 \(20\) ,所以很明显我们是可以考虑建虚树做的,这样就相当于 \(q\) 个询问,每次对于一个大小 \(\leq (40 + 1)\) 的树搞。

瞬间感觉好起来了。

又注意到 \(v\) 甚至只有 \(4\) ,也就是说我们甚至有可能可以去枚举加哪些边。

切题的话目前来看还不太行,但是大体思路基本已经定下来了。

Solution

(注:老提两点之间的路径是因为要按顺序经过给定的点,所以正过程就被分成了一段一段的路径)

发现枚举每条边加入是 \(k ^ v\) 级别的,显然不太行。

但是注意到这么一种情况,对于给定的点,我们不会去连两点之间路径上的点。因为我们要求的是简单路径,也不会说存在一种情况,能跳过两组点之间的路径,如此情况就算能够搞定其他路径是简单路径,加入的这条边也被经过了两次,所以也不行。

但除此情况外,肯定是连接两个端点更优,这样直接可以跳过该路径中的所有点。

光确定这个肯定也不行,因为这样选择还是很多。。

可以发现,按照上面的说法,每次连一条边能让一些点经过的次数 -1 ,一共就只能加 \(v\) 条边,所以那些经过次数在 \(v + 1\) 以上的就肯定不能了。

那么如果这样,只要我们能枚举加边后能快速确定点经过次数的变化的话,这道题就能在大概能搞定了。

那么现在要做的事就是:

  1. 建虚树

  2. 算每个点经过的次数

  3. 记录每两个给定点之间的路径经过了哪些点

第一个好搞,不太会的可以详见这位大佬的博客。第二个看是建图暴力找还是跳父亲,时间在 \(k\)\(k ^ 2\) 左右,不过问题不大。第三个的话,每次跳父亲时标记是第几条路径,记录到经过的点里面。

然后剩下的就交给大法师,每次枚举到一个经过次数大于 \(1\) 的点,然后枚举经过它的所有路径,把这条路径上除端点的点经过次数 -1 ,然后继续枚举加 \(v - 1\) 条边的情况,找前再判断一下是否有经过次数大于 \(v^{`} + 1\) 的点,有就不行,没有继续,直到加的边加完了还合法,那就说明可以。

两个小细节:

  1. 每次路径跳父亲找点的时候端点都要被加一次,但连续两段路径中间那个端点被算了两次,所以干脆不去管端点,最后统一端点经过次数 +1 就行。

  2. 很明显这样做的话要确定虚树上有哪些点,除了给定的 \(k\) 个点,还有一些肯恶搞要加进来的点,在建虚树时目前枚举到的点和栈头的点的 LCA 不是给定的点的时候,就说明要加新点,保存下来就行了。

时间复杂度大概 \(O(q\cdot (v + 1)! \cdot k)\)

其实像这种 \(q\) 个询问类型的题关于 \(q\) 复杂度稍稍有点紧问题也不大,所以阶乘变成了 \(v\) 次方,多个 \(k\) 什么的问题其实都不大。

Code

/*

*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
int n, q, num, k, _k, a[N], _a[N], fst[N], tot/*, ifst[N], itot*/;
int fa[N][18], dfn[N], tim, dep[N], sta[N], top, ifa[N], ilca[N];
int vis[N], iid[N], cnt[N], alo[N], numm;
struct edge {
	int nxt, to;
} e[N << 1]/*, ie[N << 1]*/;
vector<int> g[108];
inline int read() {
	char ch = getchar();
	int s = 0, w = 1;
	while (ch < '0' || ch > '9') {if(ch == '-') w = -1; ch = getchar();}
	while (ch >= '0' && ch <= '9') {s = (s << 3) + (s << 1) + ch - '0'; ch = getchar();}
	return s * w;
}
inline void add(int u, int v) {
	e[++tot] = (edge) {fst[u], v};
	fst[u] = tot;
}
/*inline void iadd(int u, int v) {
	ie[++itot] = (edge) {ifst[u], v};
	ifst[u] = itot;
}*/
inline void dfs1(int u, int ft) {
	dep[u] = dep[ft] + 1;
	dfn[u] = ++tim; fa[u][0] = ft;
	for (int i = 1; i <= 17; ++i) {
		fa[u][i] = fa[fa[u][i - 1]][i - 1];
	}
	for (int i = fst[u]; i; i = e[i].nxt) {
		int v = e[i].to;
		if (v == ft) continue;
		dfs1(v, u);
	}
}
inline int lca(int u, int v) {
	if (dep[u] < dep[v]) swap(u, v);
	for (int i = 17; ~i; --i) {
		if (dep[fa[u][i]] >= dep[v]) {
			u = fa[u][i];
		}
	}
	if (u == v) return v;
	for (int i = 17; ~i; --i) {
		if(fa[u][i] != fa[v][i]) {
			u = fa[u][i]; v = fa[v][i];
		}
	}
	return fa[u][0];
}
inline bool cmp(int x, int y) {
	return dfn[x] < dfn[y];
}
inline void IllusoryTree() {
	sort(a + 1, a + 1 + k, cmp);
	sta[top = 1] = 1; /*ifst[1] = */ifa[1] = 0;
	for (int i = 1; i <= k; ++i) {
		if (a[i] == 1) continue;
		int Lca = lca(sta[top], a[i]);
		if (sta[top] != Lca) {
			while (dfn[sta[top - 1]] > dfn[Lca]) {
				ifa[sta[top]] = sta[top - 1];
				/*iadd(sta[top - 1], sta[top]); */--top;
			}
			if (sta[top - 1] == Lca) {
				ifa[sta[top]] = sta[top - 1];
				/*iadd(sta[top - 1], sta[top]); */--top;
			}
			else {
				_a[++_k] = Lca;//避免重边
				vis[Lca] = _k;
				/*ifst[Lca] = 0;*/
				ifa[sta[top]] = Lca;
				/*iadd(Lca, sta[top]);*/
				sta[top] = Lca;
			}
		}
		sta[++top] = a[i]; /*ifst[a[i]] = 0;*/
	}
	for (int i = 1; i < top; ++i) {
		if (!vis[sta[i]]) {
			_a[++_k] = sta[i];
			vis[sta[i]] = _k;
		}
		ifa[sta[i + 1]] = sta[i];
		/*iadd(sta[i], sta[i + 1]);*/
	}
}
inline void jump(int u, int v, int id) {
	ilca[id] = -1;
	while (v) {
		if (iid[v] == numm - 1 && ilca[id] == -1) ilca[id] = v;
		iid[v] = numm; v = ifa[v];
	}
}
inline void add_belong(int u, int v, int id) {
	int _u = u, _v = v;
	while (u != ilca[id]) {
		if (u == _u) {u = ifa[u]; continue;}
		g[vis[u]].push_back(id); u = ifa[u];
	}
	while (v != ilca[id]) {
		if (v == _v) {v = ifa[v]; continue;}
		g[vis[v]].push_back(id); v = ifa[v];
	}
	if (ilca[id] != _u && ilca[id] != _v) {
		g[vis[ilca[id]]].push_back(id);
	}
}
inline void take_route(int u, int v, int id, int sig) {
	int d = (sig == 1) ? 1 : -1;
	int _u = u, _v = v;
	while (u != ilca[id]) {
		if (u == _u) {u = ifa[u]; continue;}
		cnt[u] += d; u = ifa[u];
	}
	while (v != ilca[id]) {
		if (v == _v) {v = ifa[v]; continue;}
		cnt[v] += d; v = ifa[v];
	}
	if (ilca[id] != _u && ilca[id] != _v) {
		cnt[ilca[id]] += d;
	}
}
inline bool add_edge(int avl) {
	for (int i = 1; i <= _k; ++i) {
		if (cnt[_a[i]] > avl + 1) return 0;
	}
	if (avl == 0) return 1;
	for (int i = 1; i <= _k; ++i) {
		if (cnt[_a[i]] > 1) {
			for (int id : g[i]) {
				if (!alo[id]) {
					alo[id] = 1; take_route(_a[id], _a[id + 1], id, 0);
					if (add_edge(avl - 1)) return 1;
					alo[id] = 0; take_route(_a[id], _a[id + 1], id, 1);
				}
			}
			return 0;
		}
	}
	return 1;
}
inline void mian() {
	num = read(), k = _k = read();
	for (int i = 1; i <= k; ++i) {
		a[i] = _a[i] = read();
		vis[_a[i]] = i;
	}
	IllusoryTree();
	int u = _a[1]; ++numm;
	while (u) {
		iid[u] = numm; u = ifa[u];
	}
	for (int i = 1; i < k; ++i) {
		++numm;
		jump(_a[i], _a[i + 1], i);
		add_belong(_a[i], _a[i + 1], i);
		take_route(_a[i], _a[i + 1], i, 1);
	}
	for (int i = 1; i <= k; ++i) ++cnt[_a[i]];
	if (add_edge(num)) printf("yes\n");
	else printf("no\n");
	for (int i = 1; i <= _k; ++i) {
		vis[_a[i]] = cnt[_a[i]] = alo[i] = 0;
		g[i].clear();
	}
}
int main() {
//	freopen("path.in", "r", stdin);
//	freopen("path.out", "w", stdout);
	n = read(); q = read();
	for (int i = 1; i < n; ++i) { 
		int u = read(), v = read();
		add(u, v); add(v, u);
	}
	dfs1(1, 0);
	while (q--) mian();
	return 0;
}
posted @ 2021-10-29 16:18  Illusory_dimes  阅读(37)  评论(1编辑  收藏  举报