bzoj1316 树上的询问(多个定值树链询问)

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. 

[Submit][Status][Discuss]

分析:
一开始我直接进行了Q次点分治
但是交上去8ms就WA了
怀疑自己的板子写错了,但是交上板子也是WA

实际上,点分治的常数比较大
我们做Q次点分治,不如一次点分治中处理Q个询问

tip

点分治的精髓就是cal
这道题中,我们为了降低时间复杂度而且我们要找的是长度为定值的路径
我们可以利用乘法原理进行路径统计

int cal(int now,int z)
{
    tt=0; gg=0;
    getdeep(now,0);
    sort(d+1,d+1+tt);
    g[0]=0;
    for (int i=1;i<=tt;i++)
        if (d[i]!=d[i-1]||i==1)
            g[++gg]=d[i],num[gg]=1;    //g是树链的长度 
        else num[gg]++;                //num是每种长度树链的数量

    for (int i=1;i<=Q;i++)
    {
        int l=1,r=gg;
        for (int j=1;j<=gg;j++)
            if (num[j]>1&&g[j]*2==m[i])
                ans[i]+=z*num[j]*(num[j]-1);
        while (l<r)
        {
            if (g[l]+g[r]>m[i]&&l<r) r--;
            else
            {
                if (g[l]+g[r]==m[i])
                    ans[i]+=z*num[l]*num[r];
                l++;
            }
        }
    }
}

当然,我们还是可以像以前那样进行cal
但是时间稍微慢点
这里写图片描述

int cal(int now,int z)
{
    tt=0; 
    getdeep(now,0);
    sort(d+1,d+1+tt);

    for (int i=1;i<=Q;i++)
    {
        int l=1,r=tt;
        while (l<r)
        {
            while (d[l]+d[r]>m[i]&&l<r) r--;
            int rr=r;
            if (r<=l) break;
            while (d[l]+d[r]==m[i])
            {
                ans[i]+=z;
                r--;
                if (r<=l) break;
            }
            r=rr;
            l++;
        }
    }
}
//这里写代码片
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

const int N=10010;
int f[N],d[N],sz[N],deep[N],n,m[N];
int st[N],tot=0,tt,root,sum,Q,ans[N],gg,g[N],num[N];
struct node{
    int x,y,v,nxt;
};
node way[N<<1];
bool vis[N];

void add(int u,int w,int z)
{
    tot++;
    way[tot].x=u;way[tot].y=w;way[tot].v=z;way[tot].nxt=st[u];st[u]=tot;
}

void getroot(int now,int fa)
{
    f[now]=0;
    sz[now]=1;
    for (int i=st[now];i;i=way[i].nxt)
        if (way[i].y!=fa&&!vis[way[i].y])
        {
            getroot(way[i].y,now);
            sz[now]+=sz[way[i].y];
            f[now]=max(f[now],sz[way[i].y]);
        }
    f[now]=max(f[now],sum-sz[now]);
    if (f[now]<f[root]) root=now;
}

void getdeep(int now,int fa)
{
    d[++tt]=deep[now];
    for (int i=st[now];i;i=way[i].nxt)
        if (way[i].y!=fa&&!vis[way[i].y])
        {
            deep[way[i].y]=deep[now]+way[i].v;
            getdeep(way[i].y,now);
        }
}

int cal(int now,int z)
{
    tt=0; gg=0;
    getdeep(now,0);
    sort(d+1,d+1+tt);
    g[0]=0;
    for (int i=1;i<=tt;i++)
        if (d[i]!=d[i-1]||i==1)
            g[++gg]=d[i],num[gg]=1;     //g是树链的长度
        else num[gg]++;                 //num是每种长度树链的数量

    for (int i=1;i<=Q;i++)
    {
        int l=1,r=gg;
        for (int j=1;j<=gg;j++)
            if (num[j]>1&&g[j]*2==m[i])  //两条等长链
                ans[i]+=z*num[j]*(num[j]-1);
        while (l<r)
        {
            if (g[l]+g[r]>m[i]&&l<r) r--;
            else
            {
                if (g[l]+g[r]==m[i])    //两条不等长链
                    ans[i]+=z*num[l]*num[r];
                l++;
            }
        }
    }
}

void dfs(int now)
{
    vis[now]=1;
    deep[now]=0;
    cal(now,1);
    for (int i=st[now];i;i=way[i].nxt)
        if (!vis[way[i].y])
        {
            deep[way[i].y]=way[i].v;
            cal(way[i].y,-1);
            root=0; sum=sz[way[i].y];
            getroot(way[i].y,0);
            dfs(root);
        }
}

int main()
{
    scanf("%d%d",&n,&Q);
    for (int i=1;i<n;i++)
    {
        int u,w,z;
        scanf("%d%d%d",&u,&w,&z);
        add(u,w,z); add(w,u,z);
    }

    for (int i=1;i<=Q;i++) scanf("%d",&m[i]);

    memset(vis,0,sizeof(vis));
    f[0]=N; root=0; sum=n;
    getroot(1,0);
    dfs(root);
    for (int i=1;i<=Q;i++)
        if (ans[i]>0||!m[i]) printf("Yes\n");
        else printf("No\n");

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值