题目大意:给你一棵树,边有权值,问距离不大于m的点有多少对。
男人八题之五。。果然霸气。。
最初的思路自然是O(n^2)求每对点的距离然后统计,不过这肯定会超时的。Pass。
然后就用树形DP的思路了,
选一个点为根节点向下递归,
每递归到一点p,
求都在以p为根的子树上,且不在同一以p的子节点为根的子树上且距离小于m的点对数。(也就是 最小公共祖先为p 且 距离小于m 的点对数)
然后相加得到答案。
不过这还是会TLE的...额...
在特殊数据下,比如说一条单链,上面的做法其实还是O(n^2)的复杂度,
所以要进行优化。
在每枚举到一点p时,可以看到,p往上的那些点和目前要求解的东西已经没关系了,
所以我们目前可以把以p为根的子树单独拿出来考虑。
然后现在就是求在这课子树上所有的距离小于m的点对数了,
然后为了防止出现类似单链的那种情况,
在把子树分离出来以后我们需要对子树重新定义一个根,
尽可能使得子树的最大深度最小。
比如原先 a - a - a - a - a 的一条单链,
如果以第一个a为根我们完全类似于暴力求解了,
但是以第三个a为根遍会节省许多时间。
求出a后再以上面的方法求解就行了。
说这么简单,但是真实实现时候还是有点麻烦的。
看代码吧
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 10010
#define M 20010
int m;
int head[N],ip;
bool visit[N];
int dis[N];
int f[N];
int ans;
int num;
struct
{
int son;
int maxn;//该店往下的最大深度
}node[N];
struct
{
int to;
int c;
int next;
}edge[M];
void addedge(int u,int v,int c)
{
edge[ip].to=v;
edge[ip].c=c;
edge[ip].next=head[u];
head[u]=ip++;
}
void dfs(int pos,int fa)//求每个点的子节点数以及该店往下的最大深度
{
int t;
f[num++]=pos;
node[pos].son=1;
node[pos].maxn=0;
for(int p=head[pos];p!=-1;p=edge[p].next)
{
if(edge[p].to==fa||visit[edge[p].to]) continue;
dfs(edge[p].to,pos);
node[pos].son+=node[edge[p].to].son;
if(node[edge[p].to].son>node[pos].maxn)
{
node[pos].maxn=node[edge[p].to].son;
}
}
}
int getroot(int pos,int fa)//找一个点为根使得该子树中的最大深度最小
{
num=0;
dfs(pos,fa);
int root,root1=1e9;
int temp;
for(int i=0;i<num;i++)
{
temp=max(node[f[i]].maxn,node[pos].son-node[f[i]].son);
if(temp<root1)
{
root=f[i];
root1=temp;
}
}
//其实当前求得的根并不一定使得最大深度最小
//因为node[pos].son-node[f[i]].son并不是从pos网上的最大深度
//但是也能求一下相对最优解,暂且这么用吧,影响不大
return root;
}
void getdis(int pos,int fa,int len)
{
dis[num++]=len;
for(int p=head[pos];p!=-1;p=edge[p].next)
{
if(edge[p].to==fa||visit[edge[p].to]) continue;
getdis(edge[p].to,pos,len+edge[p].c);
}
}
int count()//这个统计有个小技巧,把原先O(n^2)简化为O(n),自己可以手酸模拟一下,挺简单的
{
sort(dis,dis+num);
int l=0,r=num-1;
int ret=0;
while(l<r)
{
if(dis[l]+dis[r]<=m)
{
ret+=r-l;
l++;
}
else r--;
}
return ret;
}
void solve (int pos,int fa)
{
int root=getroot(pos,fa);//把原先以pos为根的子树分离出来,然后重新把该子树的根定义为root求解
visit[root]=1;//之前访问过的点标记,遍不会访问到pos往上的点了
num=0;
getdis(root,0,0);//在该子树下所有点到root的距离
ans+=count();
for(int p=head[ root];p!=-1;p=edge[p].next)
{
if(!visit[edge[p].to])
{
num=0;
getdis(edge[p].to,0,edge[p].c);
ans-=count();//在刚刚的统计中,如果两点的最小公共祖先是root的子节点,那么会重复计算,需要减掉
//也就是说在当前的solve函数中只求最小公共祖先为pos且距离小于m的点对
}
}
for(int p=head[root];p!=-1;p=edge[p].next)
{
if(!visit[edge[p].to])
{
solve(edge[p].to,root);//再次往下递归枚举
}
}
}
int main()
{int n,u,v,c;
while(cin>>n>>m&&n+m)
{
memset(head,-1,sizeof(head));
memset(visit,0,sizeof(visit));
ip=0;
for(int i=1;i<n;i++)
{
scanf("%d%d%d",&u,&v,&c);
addedge(u,v,c);
addedge(v,u,c);
}
visit[0]=1;
ans=0;
solve(1,0);
cout<<ans<<endl;
}
return 0;
}