【点分治】【路径小于等于k的条数】【路径恰好等于k是否存在】

POJ1741:Tree
Time Limit: 1000MS Memory Limit: 30000K
Total Submissions: 29574 Accepted: 9915

Description

Give a tree with n vertices,each edge has a length(positive integer less than 1001). 
Define dist(u,v)=The min distance between node u and v. 
Give an integer k,for every pair (u,v) of vertices is called valid if and only if dist(u,v) not exceed k. 
Write a program that will count how many pairs which are valid for a given tree. 

Input

The input contains several test cases. The first line of each test case contains two integers n, k. (n<=10000) The following n-1 lines each contains three integers u,v,l, which means there is an edge between node u and v of length l. 
The last test case is followed by two zeros. 

Output

For each test case output the answer on a single line.

Sample Input

5 4
1 2 3
1 3 1
1 4 2
3 5 1
0 0

Sample Output

8

Source

求树上路径距离小于等于k的条数。

点分治即是将树拆开,dfs处理每棵子树的过程。每次找到当前子树的重心,从重心开始分治(即是放弃父亲,不再管之前的祖先。

这道题可以用容斥计算贡献。先统计出当前整棵树的答案,减去每棵子树重复计算的不成立的答案。【注意】这里的答案都是指经过当前树的根节点的路径。

如图,如果k是4,,直接统计子树1的答案,会把1到3和1到4这条路径统计进去,而这是不成立的,所以在统计子树2多余答案时,先把1到2这条边权加进2的dis,再统计子树2中满足条件的点对,减去即可。

 

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;

int bal, asize, sum, n, k, ans;

int tov[20004], nex[20004], h[10005], stot, w[20005];

void add ( int u, int v, int s ) {
    tov[++stot] = v;
    w[stot] = s;
    nex[stot] = h[u];
    h[u] = stot;
}

int siz[10005], vis[10005];

void find_root ( int u, int f ) {
    siz[u] = 1;
    int res = 0;
    for ( int i = h[u]; i; i = nex[i] ) {
        int v = tov[i];
        if ( v == f || vis[v] ) continue;
        find_root ( v, u );
        siz[u] += siz[v];
        res = max ( res, siz[v] );
    }
    res = max ( res, sum - siz[u] );
    if ( res < asize ) {
        asize = res, bal = u;
    }
}

int dep[10004], dis[10005];

void get_dep ( int u, int f ) {
    dep[++dep[0]] = dis[u];
    siz[u] = 1;
    for ( int i = h[u]; i; i = nex[i] ) {
        int v = tov[i];
        if ( v == f || vis[v] ) continue;
        dis[v] = dis[u] + w[i];
        get_dep ( v, u );
        siz[u] += siz[v]; 
    }
}

int cal ( int u, int now ) {
    dis[u] = now; dep[0] = 0;
    get_dep ( u, 0 );
    sort ( dep + 1, dep + dep[0] + 1 );
    int tmp = 0, l = 1, r = dep[0];
    while ( l < r ) {
        if ( dep[l] + dep[r] <= k ) {
            tmp += r - l; l ++;
        } else r --;
    }
    return tmp;
}

void work ( int u ) {
    ans += cal ( u, 0 );
    vis[u] = 1;
    for ( int i = h[u]; i; i = nex[i] ) {
        int v = tov[i];
        if ( vis[v] ) continue;
        ans -= cal ( v, w[i] );
        sum = siz[v];
        asize = 0x3f3f3f3f;
        find_root ( v, u );
        work ( bal );
    }
}

int main ( ) {
    while ( scanf ( "%d%d", &n, &k ) == 2 ) {
        if ( n == 0 && k == 0 ) break;
        asize = 0x3f3f3f3f;
        stot = 0; ans = 0;
        memset ( h, 0, sizeof ( h ) );
        memset ( dis, 0, sizeof ( dis ) );
        memset ( vis, 0, sizeof ( vis ) );
        for ( int i = 1; i < n; i ++ ) {
            int a, b, c;
            scanf ( "%d%d%d", &a, &b, &c );
            add ( a, b, c );
            add ( b, a, c ); 
        }
        sum = n;
        find_root ( 1, 1 );
        work ( bal );
        printf ( "%d\n", ans );
    }
    return 0;
}

 

洛谷P3806: 【模板】点分治1

题目背景

感谢hzwer的点分治互测。

题目描述

给定一棵有n个点的树

询问树上距离为k的点对是否存在。

输入输出格式

输入格式:

 

n,m 接下来n-1条边a,b,c描述a到b有一条长度为c的路径

接下来m行每行询问一个K

 

输出格式:

 

对于每个K每行输出一个答案,存在输出“AYE”,否则输出”NAY”(不包含引号)

 

输入输出样例

输入样例#1:  复制
2 1
1 2 2
2
输出样例#1:  复制
AYE

说明

对于30%的数据n<=100

对于60%的数据n<=1000,m<=50

对于100%的数据n<=10000,m<=100,c<=1000,K<=10000000

这道题和上一道实质一样,不过我换了种写法。在统计当前树答案时,进入每棵子树,先把这棵子树的答案与之前计算过的子树答案(exist)进行比对,如果可以就更新答案,再更新exist数组,这样可以保证不会出现上面图示情况,因为计算当前子树时,不会出现子树内部互相更新的情况。

 

#include<iostream>
#include<cstdio>
using namespace std;

int n, m, qus[10005];

int stot, tov[200005], nex[200005], h[100005], w[100005];
void add ( int u, int v, int s ) {
    tov[++stot] = v;
    w[stot] = s;
    nex[stot] = h[u];
    h[u] = stot;
}

int siz[100005], asize, size, root, vis[100005], maxp[100005];
void findroot ( int u, int f ) {
    siz[u] = 1;
    for ( int i = h[u]; i; i = nex[i] ) {
        int v = tov[i];
        if ( v == f || vis[v] ) continue;
        findroot ( v, u );
        siz[u] += siz[v];
        maxp[u] = max ( maxp[u], siz[v] );
    }
    maxp[u] = max ( maxp[u], size - siz[u] );
    if ( maxp[u] < maxp[root] ) root = u;
}

int dep[100005], dis[100005];
void getdis ( int u, int f ) {
    dep[++dep[0]] = dis[u];
    for ( int i = h[u]; i; i = nex[i] ) {
        int v = tov[i];
        if ( v == f || vis[v] ) continue;
        dis[v] = dis[u] + w[i];
        getdis ( v, u );
    }
}

bool judge[1005], exist[10000005];
int q[100005], p;
void count ( int u ) {
    int p = 0;
    for ( int i = h[u]; i; i = nex[i] ) {
        int v = tov[i];
        if ( vis[v] ) continue;
        
        dep[0] = 0; dis[v] = w[i];
        getdis ( v, u );
        
        for ( int j = dep[0]; j; j -- )
            for ( int k = 1; k <= m; k ++ )
                if ( qus[k] >= dep[j] )
                judge[k] |= exist[qus[k]-dep[j]];
    
        for ( int j = dep[0]; j; j -- )
            q[++p] = dep[j], exist[dep[j]] = 1;
    }
    for ( int i = 1; i <= p; i ++ )
        exist[q[i]] = 0;
}

void work ( int u ) {
    vis[u] = 1; exist[0] = 1;
    count ( u );
    for ( int i = h[u]; i; i = nex[i] ) {
        int v = tov[i];
        if ( vis[v] ) continue;
        size = siz[v]; root = 0;
        findroot ( v, 0 );
        work ( root );
    }
}

int main ( ) {
    freopen ( "a.in", "r", stdin );
    freopen ( "a.out", "w", stdout );
    scanf ( "%d%d", &n, &m );
    for ( int i = 1; i < n; i ++ ) {
        int a, b, c;
        scanf ( "%d%d%d", &a, &b, &c );
        add ( a, b, c );
        add ( b, a, c );
    }
    
    for ( int i = 1; i <= m; i ++ )
        scanf ( "%d", &qus[i] );
    
    size = n; maxp[root] = n;
    findroot ( 1, 0 );
    work ( root );
    
    for ( int i = 1; i <= m; i ++ )
        if ( judge[i] )
            printf ( "AYE\n" );
        else    printf ( "NAY\n" );
    return 0;
}

 

转载于:https://www.cnblogs.com/wans-caesar-02111007/p/9501696.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值