HDU 5314 Happy King (点分治)

Happy King

 

Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 262144/262144 K (Java/Others)
Total Submission(s): 1077    Accepted Submission(s): 265

 

 

Problem Description

There are n cities and n−1 roads in Byteland, and they form a tree. The cities are numbered 1 through n. The population in i-th city is pi.

Soda, the king of Byteland, wants to travel from a city u to another city v along the shortest path. Soda would be happy if the difference between the maximum and minimum population among the cities passed is **no larger than** D. So, your task is to tell Soda how many different pairs (u,v) that can make him happy.

 

 

Input

There are multiple test cases. The first line of input contains an integer T (1≤T≤500), indicating the number of test cases. For each test case:

The first line contains two integers n and D (1≤n≤100000,1≤D≤109).

The second line contains n integers p1,p2,…,pn (0≤pi≤109).

Each of the following n−1 lines describing roads contains two integers u,v (1≤u,vn,uv) meaning that there is a road connecting city u and city v.

It is guaranteed that the total number of vertices in the input doesn't exceed 5×105.

 

 

Output

For each test case, output the number of different pairs that can make Soda happy.

 

 

Sample Input

 

1 3 3 1 2 3 1 2 2 3

 

 

Sample Output

6

 

题意:
第一个t,测试数据
给你n个点,每个点有权值pi,再给你n-1条边,问你有多少对(u,v)使得,u->v的最短路径上经过的点的最大权值与最小权值的差<=k

解析:
实际上就是树的点分治。
不过这里的条件是路径上经过的最大权值和最小权值。
找重心的代码就说了。

关键是在一个子树中,找到当前子树的根节点到子树中各个节点路径上的最小值和最大值,然后如果最大值-最小值<=k,那么保存在pp[]里

之后,将pp按最小值排序,这样从前往后扫一遍就可以得出,经过当前子树根节点且满足条件的(u,v)的个数

因为这里当你用Pp[i].max去找时满足条件的节点Pp[j].min(j<i)时,

Pp[i].max-Pp[i].min<=k,

pp[j].max-pp[j].min<=k

pp[j].min<=pp[i].min

=>pp[j].max<=k+pp[j].min<=pp[i].min+k

所以只要构造pp[i].max-pp[j].min<=k,那么这两个点无论如何(i点的max大,还是j点的max大),都能满足条件

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long int lli;
#define INF 9999999999

typedef struct node
{
    int u,v;
    int nxt;
}node;


typedef struct point
{
    lli _min,_max;
    bool operator < (const point& b)
    {
        return _min==b._min?_max<b._max:_min<b._min;
    }
}point;
const int MAXN = 1e5+110;

point pp[MAXN];
int n,cnt,ck,ncount;
lli k;
lli ver[MAXN];
int head[MAXN];
node edge[2*MAXN];
int visit[MAXN],siz[MAXN],maxf[MAXN];
lli ans;

void addedge(int u,int v)
{
    edge[cnt].u=u;
    edge[cnt].v=v;
    edge[cnt].nxt=head[u];
    head[u]=cnt++;
}

int wr;

void getroot(int u,int fa)
{
    siz[u]=1;
    maxf[u]=0;
    for(int i=head[u];i!=-1;i=edge[i].nxt)
    {
        int v=edge[i].v;
        if(v!=fa&&!visit[v])
        {
            getroot(v,u);
            maxf[u]=max(maxf[u],siz[v]);
            siz[u]+=siz[v];
        }
    }
    maxf[u]=max(maxf[u],ncount-siz[u]); //Count表示当前子树的结点的总个数
    if(maxf[u]<maxf[wr]) wr=u;
}

void dfs_maxmin(int u,int fa,lli maxv,lli minv)   //这里还需要重新计算每个子树的size
{                                              算出从根节点到每一个节点的路径中的最大值和最小值
    maxv=max(maxv,ver[u]);
    minv=min(minv,ver[u]);
    if(maxv-minv<=k)
    {
        pp[ck]=point{minv,maxv};
        ck++;
    }
    for(int i=head[u];i!=-1;i=edge[i].nxt)
    {
        int v=edge[i].v;
        if(v!=fa&&!visit[v])
        {
            dfs_maxmin(v,u,maxv,minv);
        }
    }


}

lli cal(int u,int _min,int _max)
{
    lli ret=0;
    ck=0;
    dfs_maxmin(u,-1,_max,_min);
    sort(pp,pp+ck);      //P+i限制了查找答案只能在比P[i].min小的点里面找,那么无论是P[i].max大还是P[j].max大,最后他与最小值的差p[j].min都小于等于k(j在[1,i)满足条件的节点)
    for(int i=ck-1;i>=0;i--)
    {
        int num=lower_bound(pp,pp+i,point{pp[i]._max-k,0})-pp;
        ret+=i-num;    //num为最远的满足条件的下标,在[num,i)之间的值都满足条件
    }
    return ret;

}

void DFS(int u)
{
    maxf[0]=ncount=siz[u];
    wr=0;
    getroot(u,-1);
    int root=wr;
    visit[root]=1;
    ans+=cal(root,INF,-INF);

    for(int i=head[root];i!=-1;i=edge[i].nxt)
    {
        int v=edge[i].v;
        if(!visit[v])
        {   //保持与前面情况一样,只是节点只有的左(右)子树的点,这样就可以找出一边的重复情况
            ans-=cal(v,ver[root],ver[root]);
            DFS(v);
        }
    }
}

int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        ans=0;
        scanf("%d%lld",&n,&k);
        for(int i=1;i<=n;i++)
            scanf("%lld",&ver[i]);
        cnt=0;
        memset(head,-1,sizeof(head));
        for(int i=1;i<n;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            addedge(u,v);
            addedge(v,u);
        }
        memset(visit,0,sizeof(visit));
        siz[1]=n;
        DFS(1);
        printf("%lld\n",ans*2);

    }
}

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值