bzoj 1316: 树上的询问 (点分治)

1316: 树上的询问

Time Limit: 10 Sec   Memory Limit: 162 MB
Submit: 564   Solved: 150
[ Submit][ Status][ Discuss]

Description

一棵n个点的带权有根树,有p个询问,每次询问树中是否存在一条长度为Len的路径,如果是,输出Yes否输出No.

Input

第一行两个整数n, p分别表示点的个数和询问的个数. 接下来n-1行每行三个数x, y, c,表示有一条树边x→y,长度为c. 接下来p行每行一个数Len,表示询问树中是否存在一条长度为Len的路径.

Output

输出有p行,Yes或No.

Sample Input

6 4
1 2 5
1 3 7
1 4 1
3 5 2
3 6 3
1
8
13
14

Sample Output

Yes
Yes
No
Yes


HINT

30%的数据,n≤100. 
100%的数据,n≤10000,p≤100,长度≤1000000. 

做完此题可看下POJ 3237 Tree

Source

[ Submit][ Status][ Discuss]

题解:点分治

#include<iostream>  
#include<cstdio>  
#include<algorithm>  
#include<cmath>  
#include<cstring>  
#define N 30003  
using namespace std;  
int n,m,tot,num[10000003];  
int point[N],next[N],v[N],len[N],cnt,root,sum,vis[N];  
int f[N],deep[N],mp[N],d[N],ak[N],mark[N],son[N],g[N];  
void add(int x,int y,int z)  
{  
    tot++; next[tot]=point[x]; point[x]=tot; v[tot]=y; len[tot]=z;  
    tot++; next[tot]=point[y]; point[y]=tot; v[tot]=x; len[tot]=z;  
    //cout<<x<<" "<<y<<" "<<z<<endl;  
}  
void getroot(int x,int fa)  
{  
    son[x]=1; f[x]=0;  
    for (int i=point[x];i;i=next[i]){  
        if (vis[v[i]]||v[i]==fa) continue;  
        getroot(v[i],x);  
        son[x]+=son[v[i]];  
        f[x]=max(f[x],son[v[i]]);  
    }  
    f[x]=max(f[x],sum-son[x]);  
    if (f[x]<f[root]) root=x;  
}  
void getdeep(int x,int fa)  
{  
    deep[++deep[0]]=d[x];  
    for (int i=point[x];i;i=next[i]){  
        if (vis[v[i]]||v[i]==fa) continue;  
        d[v[i]]=d[x]+len[i];  
        getdeep(v[i],x);  
    }  
}  
void cal(int x,int now,int opt)  
{  
    d[x]=now; deep[0]=0;  
    getdeep(x,0);  
    sort(deep+1,deep+deep[0]+1);  
    g[0]=0;  
    for (int i=1;i<=deep[0];i++)  
     if (deep[i]!=deep[i-1]||i==1)   
      g[++g[0]]=deep[i],num[g[0]]=1;  
     else num[g[0]]++;  
    for (int i=1;i<=m;i++) {  
       int l=1; int r=g[0];  
       for (int j=1;j<=g[0];j++)   
        if (num[j]>1&&g[j]*2==ak[i])  
         mark[i]+=opt*num[j]*(num[j]-1);   
       while (l<r) {  
          if (g[l]+g[r]<ak[i]) l++;  
          else {  
             if (g[l]+g[r]==ak[i]) mark[i]+=opt*num[l]*num[r];  
             r--;  
           }  
       }  
    }  
}  
void work(int x)  
{  
    cal(x,0,1);  
    vis[x]=1;  
    for (int i=point[x];i;i=next[i]){  
        if (vis[v[i]]) continue;  
        cal(v[i],len[i],-1);  
        sum=son[v[i]]; root=0;  
        getroot(v[i],x);  
        work(root);  
    }  
}  
int main()  
{  
    freopen("a.in","r",stdin);  
    freopen("my.out","w",stdout);  
    scanf("%d%d",&n,&m);  
    for (int i=1;i<n;i++) {  
        int x,y,z; scanf("%d%d%d",&x,&y,&z);  
        add(x,y,z);  
    }  
    for (int i=1;i<=m;i++) scanf("%d",&ak[i]);  
    sum=n; f[0]=1000000000; root=0;  
    getroot(1,0);  
    work(root);  
    for (int i=1;i<=m;i++)  
     if(mark[i]>0||!ak[i]) printf("Yes\n");  
     else printf("No\n");  
}  


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值