一道树上差分的入手好题,中文题面不再赘述
读完题后,我们发现,答案就是图中的点被经过的最多次数,即有多少条路径经过它,暴力期望得分20~40分,正解是什么呢?
树上差分+LCA
没错,和差分的操作一样,在区间的左端点加一,在右端点+1的位置减一,只不过放到了树上,我们先预处理每个点的LCA,之后我们发现了一些树上差分的性质
1.假如我们现在有一条从u->v的路径,那么假如u和v的lca为a,那么u->v的路径必定可以拆分成u->a,a->v,为什么呢?我们发现,想从u的上方到达u,必定要经过他的父亲,同理,u若想到达他上面的点,必定要经过它的父节点,因为只有这条路径连接着外面
2.一个点只有一个直接父节点 (我在废话,不然怎么叫树??233),两点之间只有一条路径(看1)
然后我们每读入一组起始点,就求一遍他们的LCA,然后将两点之间的路径拆分成起点到LCA和终点到LCA,由差分的性质,两条路径上的点都加一,等价于起点加一,终点加一,然后划重点了昂,右端点加一是什么呢?LCA的父节点呗,那么我们发现LCA这个点会被算两遍!所以我们把两个链变成u->LCA-1,LCA->v,那么我们找到LCA后,将sum[lca]--,sum[fa[lca][0]]--,sum[u]++,sum[v]++,然后这个问题就顺利解决了
最后求解就是在所有的sum中取最大
代码
//By AcerMo
#include<cmath>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int M=50050;
vector<int>v[M*4];
int n,m,st,ed;
int dep[M],sum[M];
int fa[M][30],cnt,ans;
void built(int x,int fatt)
{
dep[x]=dep[fatt]+1;
fa[x][0]=fatt;
for (int i=1;(1<<i)<=dep[x];i++)
fa[x][i]=fa[fa[x][i-1]][i-1];
for (int i=0;i<v[x].size();i++)
{
int go=v[x][i];
if (go!=fatt) built(go,x);
}
return ;
}
void lca(int a,int b)
{
int ans;
sum[a]++;sum[b]++;
if (dep[a]<dep[b]) swap(a,b);
for (int i=28;i>=0;i--)
if (dep[fa[a][i]]>=dep[b])
a=fa[a][i];
if (a==b) ans=a;
else
{
for (int i=28;i>=0;i--)
if (fa[a][i]!=fa[b][i])
a=fa[a][i],b=fa[b][i];
ans=fa[a][0];
}
sum[ans]--;sum[fa[ans][0]]--;
return ;
}
void getsum(int x,int fatt)
{
for (int i=0;i<v[x].size();i++)
{
int go=v[x][i];
if (go==fatt) continue;
getsum(go,x);
sum[x]+=sum[go];
}
ans=max(ans,sum[x]);
return ;
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<n;i++)
{
scanf("%d%d",&st,&ed);
v[st].push_back(ed);
v[ed].push_back(st);
}
built(1,0);
for (int i=1;i<=m;i++)
{
scanf("%d%d",&st,&ed);
lca(st,ed);
}
getsum(1,0);
cout<<ans;
return 0;
}