POJ - 2114,点分治

Boatherds

Boatherds Inc. is a sailing company operating in the country of Trabantustan and offering boat trips on Trabantian rivers. All the rivers originate somewhere in the mountains and on their way down to the lowlands they gradually join and finally the single resulting river flows to the sea. Moreover, the Trabantian villages are exactly at the rivers’ springs, junctions and at the mouth of the largest river. Please note that more than 2 rivers can join at a junction. However, the rivers always form a tree (with villages as vertices).

The pricing policy of the Boatherds is very simple: each segment of each river between two villages is assigned a price (the price is same in both directions), so if a tourist requests a journey between any two villages, the ticket office clerks just add the prices of the segments along the only path between the villages.

One day, a very strange tourist appeared. She told the clerks that she returns to her country on the next day and she wants to spend all the remaining money on a boat trip, so they should find a route with exactly this cost. Being just poor (ahem) businessmen, they have asked the Abacus Calculator Makers for help.

You are given a description of the river network with costs of river segments and a sequence of integers x1,…, xk. For each xi, you should determine if there is a pair of cities (a, b) in the river network such that the cost of the trip between a and b is exactly xi.

除了输入方式不同,其他和P3806同,详解见https://blog.csdn.net/xing_mo/article/details/104005955

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXN 10010
#define INF 0x3f3f3f3f
using namespace std;
int head[MAXN],tot;
struct edge
{
    int v,w,nxt;
}edg[MAXN << 1];
inline void addedg(int u,int v,int w)
{
    edg[tot].v = v;
    edg[tot].w = w;
    edg[tot].nxt = head[u];
    head[u] = tot++;
}
int n,root,ms,mson[MAXN],sz[MAXN],Size;
bool vis[MAXN];
//root用于标记重心,ms表示树的重心的最大子树的大小,mson[i]记录以i为根最大子树的大小
//sz[i]记录以i为根子树的大小,Size表示当前整棵树的大小,vis[i]表示当前节点是否被分治过
void getroot(int u,int f)//获得重心
{
    sz[u] = 1,mson[u] = 0;
    int v;
    for(int i = head[u];i != -1;i = edg[i].nxt)
    {
        v = edg[i].v;
        if(vis[v] || v == f) continue;//剔除已经被分治过的点
        getroot(v,u);
        sz[u] += sz[v];
        if(sz[v] > mson[u]) mson[u] = sz[v];
    }
    if(Size - sz[u] > mson[u]) mson[u] = Size-sz[u];//把u看作根节点时u的父亲那一部分也算作子树
    if(ms > mson[u]) ms = mson[u],root = u;//更新重心
}
int m,ask[110],ans[110];//所有询问
int dis[MAXN],cnt;//dis记录所有节点到重心的距离
struct node
{
    int d,num;
}nod[MAXN];//记录距离d出现的次数num
int cc;//不同距离数
void getdis(int u,int f,int d)//获得到目标点的距离
{
    dis[++cnt] = d;
    int v;
    for(int i = head[u];i != -1;i = edg[i].nxt)
    {
        v = edg[i].v;
        if(vis[v] || v == f) continue;
        getdis(v,u,d + edg[i].w);
    }
}
void cal(int u,int d,int tp)//u表示getdis的起点,d表示u到目标点的距离,tp表示这一次统计出来的答案是合理的还是不合理的
{
    cnt = 0;
    getdis(u,0,d);//算出树中的点到目标点的距离
    sort(dis+1,dis+cnt+1);
    cc = 1,nod[cc].d = dis[1],nod[cc].num = 1;
    for(int i = 2;i <= cnt;++i)
        if(dis[i] != dis[i-1]) nod[++cc].d = dis[i],nod[cc].num = 1;
        else ++nod[cc].num;
    for(int i = 1;i <= m;++i)
    {
        int l = 1,r = cc;
        while(l <= r)
        {
            if(nod[l].d + nod[r].d == ask[i])
            {
                if(l == r) ans[i] += nod[l].num*(nod[l].num-1)/2 * tp;
                else ans[i] += nod[l].num * nod[r].num * tp;
                ++l,--r;
            }
            else if(nod[l].d + nod[r].d < ask[i])
                ++l;
            else
                --r;
        }
    }
}
void solve(int u,int ssize)//ssize是当前这棵子树的大小
{
    vis[u] = true;//代码保证每次进来的u都必定是当前这棵树的重心,我们将vis[u]标记为true,表示u点被分治过
    cal(u,0,1);//计算这棵树以u为重心的所有组合,但包括了共用同一条边的情况
    int v;
    for(int i = head[u];i != -1;i = edg[i].nxt)
    {
        v = edg[i].v;
        if(vis[v]) continue;
        cal(v,edg[i].w,-1);//将共用一条边的不合法情况去除
        ms = INF;//记得每次都要初始化
        Size = sz[v] < sz[u]?sz[v]:(ssize-sz[u]);//因为v实际上可能是u的父亲,故sz需相减
        getroot(v,v);//求出以v为根节点的子树重心
        solve(root,Size);
    }
}
inline void init()
{
    tot = 0,ms = INF,Size = n;
    memset(head,-1,sizeof(int)*(n+1));
    memset(vis,false,sizeof(bool)*(n+1));
}
int main()
{
    while(~scanf("%d",&n) && n)
    {
        init();
        int v,w;
        for(int u = 1;u <= n;++u)
        {
            while(scanf("%d",&v))
            {
                if(v)
                {
                    scanf("%d",&w);
                    addedg(u,v,w),addedg(v,u,w);
                }
                else
                    break;
            }
        }
        m = 0;
        while(scanf("%d",&v))
        {
            if(v)
                ask[++m] = v;
            else
                break;
        }
        memset(ans,0,sizeof(int)*(m+1));
        getroot(1,1);
        solve(root,Size);
        for(int i = 1;i <= m;++i)
            if(ans[i] > 0) printf("AYE\n");
            else printf("NAY\n");
        puts(".");
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值