洛谷P5838 [USACO19DEC]Milk Visits G 题解
题目链接:P5838 [USACO19DEC]Milk Visits G
题意:
Farmer John 计划建造 $N$ 个农场,用 $N-1$ 条道路连接,构成一棵树(也就是说,所有农场之间都互相可以到达,并且没有环)。每个农场有一头奶牛,品种为 $1$ 到 $N$ 之间的一个整数 $T_i$ 。
Farmer John 的 $M$ 个朋友经常前来拜访他。在朋友 $i$ 拜访之时,Farmer John 会与他的朋友沿着从农场 $A_i$ 到农场 $B_i$ 之间的唯一路径行走(可能有 $A_i = B_i$)。除此之外,他们还可以品尝他们经过的路径上任意一头奶牛的牛奶。由于 Farmer John 的朋友们大多数也是农场主,他们对牛奶有着极强的偏好。他的每个朋友都只喝某种特定品种的奶牛的牛奶。任何 Farmer John 的朋友只有在他们访问时能喝到他们偏好的牛奶才会高兴。
请求出每个朋友在拜访过后是否会高兴。
输入格式:
输入的第一行包含两个整数 $N$ 和 $M$。
第二行包含 $N$ 个空格分隔的整数 $T_1,T_2,\ldots, T_N$。第 $i$ 个农场内的奶牛的品种用 $T_i$ 表示。
以下 $N-1$ 行,每行包含两个不同的整数 $X$ 和 $Y$($1 \leq X, Y \leq N$),表示农场 $X$ 与 $Y$ 之间有一条边。
以下 $M$ 行,每行包含整数 $A_i$,$B_i$,以及$C_i$。$A_i$ 和 $B_i$ 表示朋友 $i$ 拜访时行走的路径的端点,$C_i$($1\le C_i\le N$)表示这个朋友喜欢的牛奶的奶牛品种。
输出格式:
输出一个长为 $M$ 的二进制字符串。如果第 $i$ 个朋友会感到高兴,则字符串的第 $i$ 个字符为
1
,否则为0
。数据范围:
$1 \leq N \leq 10^5$,$1 \leq M \leq 10^5$。
知道为啥我刚要写个 Tarjan 求 LCA 了吧,就是因为这题的最优解用到了它的思想。
注意到 $u,v$ 间有目标颜色,记 $d=\mathrm{LCA}(u,v)$ ,则 $[u,d]\cup[d,v]$ 内至少有一个。
考虑维护 $1 \leadsto u$ 中每个颜色最深在哪里,记它在 $1 \leadsto u$ 上的下个节点为 $\mathtt{top}$ 。
考虑当询问的左节点被访问时记录该颜色对应的 $\mathtt{top}$ ,然后当右节点被访问时校验是否相等
容易发现,如果 $\mathtt{top}$ 不相等,则这个点在 $u \leadsto v$ 上(记录「下个节点」的原因,考虑 $\mathrm{LCA}(u,v)$ )
时间复杂度 $\mathcal{O}(n + m)$
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
#define N ((int)(1e5 + 15))
vector<int> vec[N];
int n,m,pos=1,head[N],c[N],q[N],top[N],ans[N];
struct Edge { int u,v,next; } e[N * 2];
void addEdge(int u,int v) { e[++pos] = {u,v,head[u]}; head[u] = pos; }
void dfs(int u,int f)
{
int tmp = top[c[u]];
for(int i : vec[u])
{
if(ans[i] != -1) ans[i] = (ans[i] != top[q[i]]);
else ans[i] = top[q[i]];
}
for(int i = head[u]; i; i = e[i].next)
{ int v = e[i].v; if(v != f) { top[c[u]] = v; dfs(v,u); } }
top[c[u]] = tmp;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
cin >> n >> m;
for(int i = 1; i <= n; i++) cin >> c[i];
for(int i = 1, u,v; i < n; i++) { cin >> u >> v; addEdge(u,v); addEdge(v,u); }
for(int i = 1,u,v; i <= m; i++)
{
cin >> u >> v >> q[i];
if(c[u] == q[i] || c[v] == q[i]) ans[i] = 1;
else { ans[i] = -1, vec[u].emplace_back(i); vec[v].emplace_back(i); }
}
dfs(1,0); for(int i = 1; i <= m; i++) cout << ans[i];
return 0;
}
当然这道题也是有「通俗易懂的数据结构解法」的啦。
题目中的「询问 $u \leadsto v$ 是否存在颜色为 $c$ 的节点」,其实就是序列上 $[l,r]$ 是否存在颜色为 $c$ 的位置。
这种我们直接用 vector 维护每个颜色的出现位置,然后二分一下就好了,这道题无非就是套了个树剖。
时间复杂度 $\mathcal{O}(n \log^2 n)$ ,除非特意构造否则一般难卡满。
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
#define N ((int)(1e5 + 15))
vector<int> vec[N];
int n,m,pos=1,head[N],c[N],top[N],id[N],fa[N],son[N],sz[N],dep[N];
struct Edge { int u,v,next; } e[N * 2];
void addEdge(int u,int v) { e[++pos] = {u,v,head[u]}; head[u] = pos; }
void dfs(int u,int f,int d)
{
dep[u] = d; fa[u] = f; sz[u] = 1; int mx = -1;
for(int i = head[u]; i; i = e[i].next)
{
int v = e[i].v; if(v == f) continue;
dfs(v,u,d+1); sz[u] += sz[v]; if(sz[v] > mx) { mx = sz[v], son[u] = v; }
}
}
void dfs(int u,int ftop)
{
static int tot = 0; top[u] = ftop; id[u] = ++tot;
vec[c[u]].emplace_back(tot); if(son[u]) dfs(son[u], ftop);
for(int i = head[u]; i; i = e[i].next)
{ int v = e[i].v; if(v != fa[u] && v != son[u]) dfs(v,v); }
}
bool query(int nl,int nr,int k)
{ return *lower_bound(vec[k].begin(), vec[k].end(), nl) <= nr; }
bool qRange(int x,int y,int k)
{
while(top[x] != top[y])
{
if(dep[top[x]] < dep[top[y]]) swap(x,y);
if(query(id[top[x]], id[x], k)) return 1; x = fa[top[x]];
}
if(dep[x] > dep[y]) swap(x,y);
return query(id[x], id[y], k);
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
cin >> n >> m;
for(int i = 1; i <= n; i++) cin >> c[i];
for(int i = 1, u,v; i < n; i++)
{ cin >> u >> v; addEdge(u,v); addEdge(v,u); }
dfs(1,0,1); dfs(1,1); for(int i = 1; i <= n; i++) vec[i].emplace_back(INF);
for(int i = 1, u,v,w; i <= m; i++)
{ cin >> u >> v >> w; cout << qRange(u,v,w); }
return 0;
}
参考文献:
[1] https://www.luogu.com.cn/blog/184549/ti-xie-p5838-usaco19decmilk-visits-g