【点分治】【路径小于等于k的条数】【路径恰好等于k是否存在】
POJ1741:Tree
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; }
题目背景
感谢hzwer的点分治互测。
题目描述
给定一棵有n个点的树
询问树上距离为k的点对是否存在。
输入输出格式
输入格式:
n,m 接下来n-1条边a,b,c描述a到b有一条长度为c的路径
接下来m行每行询问一个K
输出格式:
对于每个K每行输出一个答案,存在输出“AYE”,否则输出”NAY”(不包含引号)
输入输出样例
说明
对于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; }