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\) 以上的就肯定不能了。
那么如果这样,只要我们能枚举加边后能快速确定点经过次数的变化的话,这道题就能在大概能搞定了。
那么现在要做的事就是:
-
建虚树
-
算每个点经过的次数
-
记录每两个给定点之间的路径经过了哪些点
第一个好搞,不太会的可以详见这位大佬的博客。第二个看是建图暴力找还是跳父亲,时间在 \(k\) 到 \(k ^ 2\) 左右,不过问题不大。第三个的话,每次跳父亲时标记是第几条路径,记录到经过的点里面。
然后剩下的就交给大法师,每次枚举到一个经过次数大于 \(1\) 的点,然后枚举经过它的所有路径,把这条路径上除端点的点经过次数 -1 ,然后继续枚举加 \(v - 1\) 条边的情况,找前再判断一下是否有经过次数大于 \(v^{`} + 1\) 的点,有就不行,没有继续,直到加的边加完了还合法,那就说明可以。
两个小细节:
-
每次路径跳父亲找点的时候端点都要被加一次,但连续两段路径中间那个端点被算了两次,所以干脆不去管端点,最后统一端点经过次数 +1 就行。
-
很明显这样做的话要确定虚树上有哪些点,除了给定的 \(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;
}