题目网址
https://acm.ecnu.edu.cn/problem/3631/
LCA倍增
倍增利用了二进制的特性;LCA为求公共祖先。
利用倍增的典型算法还有RMQ。
题目代码
#include<cstdio>
#include<vector>
#include<string>
#include<cstring>
#include<algorithm>
using namespace std;
int f[200005],deep[200005],w[200005],p[200005][30];
int use[200005];
int n;
vector<int> vt[200005];
int dfs(int u,int pre,int t)//更新deep,f数组
{
deep[u]=t;
f[u]=pre;
for(int i=0;i<vt[u].size();i++)
{
int v=vt[u][i];
if(v!=pre)
{
dfs(v,u,t+1);
}
}
}
void init()//对p数组初始化
{
for(int j=0;(1<<j)<=n;j++)
for(int i=1;i<=n;i++)
p[i][j]=-1;
for(int i=1;i<=n;i++) p[i][0]=f[i];
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i<=n;i++)
if(p[i][j-1]!=-1)
p[i][j]=p[p[i][j-1]][j-1];//二进制的思想
//2^j=2^(j-1)+2^(j-1)
//i的第2^j祖先就是i的第2^(j-1)祖先的第2^(j-1)祖先
}
int lca(int a,int b)//求最近公共祖先
{
int i,j;
if(deep[a]<deep[b])swap(a,b);
for(i=0;(1<<i)<=deep[a];i++);
i--;//i即为b若想跟a深度相同,b能跳跃最大的2^i
//倍增法,每次向上进深度2^j,找到最近公共祖先的子结点
for(j=i;j>=0;j--) //这种方法将两点上升所需的复杂度减低
if(deep[a]-(1<<j)>=deep[b])
a=p[a][j];
if(a==b) return a;//此时a,b两点深度相同
for(j=i;j>=0;j--)
{
if(p[a][j]!=-1&&p[a][j]!=p[b][j])
{
a=p[a][j];
b=p[b][j];
}
}
return f[a];
}
int dfs2(int u,int fa)
{
int len=vt[u].size();
for(int i=0;i<len;i++)
{
int v=vt[u][i];
if(v!=fa)
{
dfs2(v,u);
use[u]+=use[v];
}
}
}
bool cmp(int h1,int h2)
{
return h1>h2;
}
int main()
{
while(~scanf("%d",&n))
{
int m,a,b,c;
memset(use,0,sizeof(use));
memset(deep,0,sizeof(deep));
memset(f,0,sizeof(f));
memset(w,0,sizeof(w));
for(int i=1;i<=n;i++) vt[i].clear();
for(int i=1;i<n;i++)
{
scanf("%d%d%d",&a,&b,&w[i]);
vt[a].push_back(b);
vt[b].push_back(a);
}
sort(w+1,w+n);
w[n]=0;
dfs(1,1,0);
init();
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&a,&b);
int ans=lca(a,b);
use[ans]-=2;//差分
use[a]++;
use[b]++;
}
dfs2(1,1);
sort(use+1,use+1+n,cmp);
long long sum=0;
for(int i=1;i<=n;i++)
sum=sum+use[i]*w[i];//通过次数最多的路给最小的w
printf("%lld\n",sum);
}
}