从题目部分分的提示中可以获得很多想法,比如说第六个点树退化成一条链,第九个点所有的s=1,第15个点所有的t=1
然后思考一下,每一条最短路都是一条链,LCA必定存在于每一条链上,每条链可以分为
s->lca和lca->t两部分
设deep[i]为i点的深度,dis为S到T最短路
先假设某个w[i]可以看到玩家,那么w[i]满足的等式是
{deep[S]=w[i]+deep[i]deep[T]−deep[i]=dis−w[i]w[i]∈(S→lca)w[i]∈(lca→T){deep[S]=w[i]+deep[i]w[i]∈(S→lca)deep[T]−deep[i]=dis−w[i]w[i]∈(lca→T)
然后这道题就是对于每一个玩家,统计有多少个满足条件的w[i]就好了,需要用桶的思想去解决,网上有很多题解,这里就不赘述了(这才是这道题的最难点)
但是会统计到不在S到T最短路上的点,删除方式是在进入i的子树之前算算有多少个满足的,退出i的子树后算算有多少个满足的,用新的减去旧的,多出来的就是i的子树中的点
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
using namespace std;
#define debug(x) cerr << #x << "=" << x << endl;
const int MAXN = 300010*2;
const int MAXM = 300010;
int n,m,tot,last[MAXN],vis[MAXN],depth[MAXN],f[MAXN][25],mt;
int maxdepth,w[MAXN],barrel[MAXN],st[MAXN],ans[MAXN];
vector <int> dif1[MAXN], dif2[MAXN], dif3[MAXN];
struct Pla{
int s,t,lca,len;
}pla[MAXN];
inline void r(int &x) {
x = 0;
char ch = getchar();
while(ch < '0' || ch > '9') ch = getchar();
while(ch >='0' && ch <='9') x = x*10+ch-'0', ch = getchar();
return;
}
struct Edge{
int u,v,to;
Edge(){}
Edge(int u, int v, int to) : u(u), v(v), to(to) {}
}e[MAXM*2];
inline void add(int u, int v) {
e[++tot] = Edge(u,v,last[u]);
last[u] = tot;
}
void init_dfs(int x) {
for(int i=last[x]; i; i=e[i].to) {
int v = e[i].v;
if(depth[v]) continue;
depth[v] = depth[x] + 1;
maxdepth = max(maxdepth,depth[v]);
f[v][0] = x;
init_dfs(v);
}
}
void init_lca() {
mt = log(n) / log(2) + 1;
for(int k=1; k<=mt; k++)
for(int i=1; i<=n; i++)
f[i][k] = f[f[i][k-1]][k-1];
}
int lca(int a, int b) {
if(depth[a] < depth[b]) swap(a,b);
for(int k=mt; k>=0; k--) {
if(depth[f[a][k]] >= depth[b]) {
a = f[a][k];
}
}
if(a == b) return a;
for(int k=mt; k>=0; k--) {
if(f[a][k] != f[b][k]) {
a = f[a][k];
b = f[b][k];
}
}
return f[a][0];
}
void dfs_up(int x) {
int now = depth[x] + w[x];
int temp = barrel[now];
vis[x] = 1;
barrel[depth[x]] += st[x];
for(int i=last[x]; i; i=e[i].to) {
int v = e[i].v;
if(vis[v]) continue;
dfs_up(v);
}
ans[x] += barrel[now] - temp;
for(int i=dif1[x].size()-1; i>=0; i--) {
barrel[dif1[x][i]]--;//删除以x为lca的玩家,这样比x高的点就不会受到这个玩家的贡献
}
}
void dfs_down(int x) {
vis[x] = 1;
int now = depth[x] - w[x] + 300000;
int temp = barrel[now];
for(int i=last[x]; i; i=e[i].to) {
int v = e[i].v;
if(vis[v]) continue;
dfs_down(v);
}
for(int i=dif2[x].size()-1; i>=0; i--) {
barrel[dif2[x][i]+300000]++;
}
ans[x] += barrel[now] - temp;
for(int i=dif3[x].size()-1; i>=0; i--) {
barrel[dif3[x][i]+300000]--;
}
}
int main() {
r(n),r(m);
for(int i=1; i<n; i++) {
int u,v;
r(u),r(v);
add(u,v);
add(v,u);
}
depth[1] = 1; //¸ù½ÚµãÉî¶ÈΪ1
init_dfs(1);
for(int i=1; i<=n; i++)
r(w[i]);
init_lca();
for(int i=1; i<=m; i++) {
r(pla[i].s), r(pla[i].t);
st[pla[i].s]++;
pla[i].lca = lca(pla[i].s, pla[i].t);
pla[i].len = depth[pla[i].s] + depth[pla[i].t] - 2 * depth[pla[i].lca];
dif1[pla[i].lca].push_back(depth[pla[i].s]);
dif2[pla[i].t].push_back(depth[pla[i].t] - pla[i].len);
dif3[pla[i].lca].push_back(depth[pla[i].t] - pla[i].len);
}
dfs_up(1);
memset(vis,0,sizeof(vis));
memset(barrel,0,sizeof(barrel));
dfs_down(1);
for(int i=1; i<=m; i++)
if(depth[pla[i].s] - depth[pla[i].lca] == w[pla[i].lca])
ans[pla[i].lca]--;
for(int i=1; i<=n; i++)
printf("%d ",ans[i]);
return 0;
}

本文探讨了一种特定的图论问题,即如何通过计算最短路径来判断哪些节点可以被特定玩家看到。利用深度优先搜索和最近公共祖先(LCA)算法,文章详细介绍了如何统计满足条件的节点,并采用桶排序的方法来优化计算过程。
1793

被折叠的 条评论
为什么被折叠?



