题意:给你一颗n(n<=40000)的树,求两个点的距离<=k的点的对数
题解:
关于树的分治,这我还是第一次遇到,决定好好总结下,以备以后再遇到这种类型的题
分治要分的均匀,而要分治一颗树,最好的位置自然是树的重心,由重心开始,分治子树
这道题,对于每颗子树,点对之间的距离分为两种 1.过根节点 2.不过根节点
很显然,第二种情况可以作为子情况讨论。在计算一颗树的过根节点的点对数
是,还要减去其两个点在同一子树中的情况,避免重复计算
code:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
const int MAXN=40005;
const int inf=0x3f3f3f3f;
using namespace std;
struct node{
int e,next,v;
}h[MAXN<<1];
int n,m,cnt,fir[MAXN];
int num[MAXN],f[MAXN];
int root,allnode,d[MAXN];
int deep[MAXN],ans,K;
bool vis[MAXN];
inline void addedge(int t1,int t2,int v){
h[++cnt].e=t2;
h[cnt].next=fir[t1];
fir[t1]=cnt;
h[cnt].v=v;
}
void Getroot(int s,int fa){
num[s]=1; f[s]=0;
for(int i=fir[s];i;i=h[i].next)
{
int e=h[i].e;
if(e==fa||vis[e]) continue;
Getroot(e,s);
num[s]+=num[e];
f[s]=max(f[s],num[e]);
}
f[s]=max(f[s],allnode-num[s]);
if(f[s]<f[root]) root=s;
}
void Getdeep(int s,int fa){
if(d[s]<=K) deep[++deep[0]]=d[s];
for(int i=fir[s];i;i=h[i].next)
{
int e=h[i].e;
if(e==fa||vis[e]) continue;
d[e]=d[s]+h[i].v;
Getdeep(e,s);
}
}
int cal(int s,int now){//返回以s为根的子树中,当前深度为now有多少对点在K之内
d[s]=now; deep[0]=0;
Getdeep(s,0); int all=0;
sort(deep+1,deep+deep[0]+1);
//这里其中一个从小到大枚举,一个从大到小枚举,
//找到第一个r使得当前l成立,那么对于l,在l,r的所有点对都满足
//始终保证l<r,以免重复枚举
//这里也包含了枚举的技巧,巧妙地用到了之前求出的值
//如果之前的l,r不满足,那么l+1,r也一定不满足,满足只可能是l,r-1
//如果之前的l,r满足,那么l+1,对于不满足l,r的点,自然也不满足
for(int l=1,r=deep[0];l<r;)
{
if(deep[l]+deep[r]<=K){all+=r-l;l++;}
else r--;
}
return all;
}
void solve(int s){
ans+=cal(s,0); vis[s]=1;
for(int i=fir[s];i;i=h[i].next)
{
int e=h[i].e;
if(vis[e]) continue;
ans-=cal(e,h[i].v);//减去在同一颗子树中的点对,避免重复计算
allnode=num[e];
root=0; Getroot(e,s);//分治
solve(root);
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int s,e,v; char t[5];
scanf("%d%d%d%s",&s,&e,&v,t);
addedge(s,e,v); addedge(e,s,v);
}
scanf("%d",&K);
allnode=n; f[0]=inf;
Getroot(1,0); solve(root);//找到重心,从重心处分治
printf("%d\n",ans);
}