【bzoj1316】树上的询问 树的点分治+STL-set
题目描述
一棵n个点的带权有根树,有p个询问,每次询问树中是否存在一条长度为Len的路径,如果是,输出Yes否输出No.
输入
第一行两个整数n, p分别表示点的个数和询问的个数. 接下来n-1行每行三个数x, y, c,表示有一条树边x→y,长度为c. 接下来p行每行一个数Len,表示询问树中是否存在一条长度为Len的路径.
输出
输出有p行,Yes或No.
样例输入
6 4
1 2 5
1 3 7
1 4 1
3 5 2
3 6 3
1
8
13
14
样例输出
Yes
Yes
No
Yes
题解
树的点分治+STL-set
由于有多组询问,所以可以考虑把询问离线,然后一起处理(貌似暴力也能过)。
然后就是点分治的经典题目了。
每次找子树是查询以前的某深度与现在的某深度形成的路径长度是否为l。这个可以使用set维护。
时间复杂度$O(nq\log^2n)=O(能过)$。
#include <set> #include <cstdio> #define N 10010 using namespace std; set<int> s; int m , a[N] , head[N] , to[N << 1] , len[N << 1] , next[N << 1] , cnt , si[N] , mx[N] , sum , root , deep[N] , vis[N] , d[N] , tot; bool ans[N]; void add(int x , int y , int z) { to[++cnt] = y , len[cnt] = z , next[cnt] = head[x] , head[x] = cnt; } void getroot(int x , int fa) { int i; si[x] = 1 , mx[x] = 0; for(i = head[x] ; i ; i = next[i]) if(!vis[to[i]] && to[i] != fa) getroot(to[i] , x) , si[x] += si[to[i]] , mx[x] = max(mx[x] , si[to[i]]); mx[x] = max(mx[x] , sum - si[x]); if(mx[x] < mx[root]) root = x; } void dfs(int x , int fa) { int i; si[x] = 1 , d[++tot] = deep[x]; for(i = head[x] ; i ; i = next[i]) if(!vis[to[i]] && to[i] != fa) deep[to[i]] = deep[x] + len[i] , dfs(to[i] , x) , si[x] += si[to[i]]; } void solve(int x) { int i , j , k; vis[x] = 1 , s.clear() , s.insert(0); for(i = head[x] ; i ; i = next[i]) { if(!vis[to[i]]) { tot = 0 , deep[to[i]] = len[i] , dfs(to[i] , 0); for(j = 1 ; j <= tot ; j ++ ) for(k = 1 ; k <= m ; k ++ ) if(s.find(a[k] - d[j]) != s.end()) ans[k] = 1; for(j = 1 ; j <= tot ; j ++ ) s.insert(d[j]); } } for(i = head[x] ; i ; i = next[i]) if(!vis[to[i]]) sum = si[to[i]] , root = 0 , getroot(to[i] , 0) , solve(root); } int main() { int n , i , x , y , z; scanf("%d%d" , &n , &m); for(i = 1 ; i < n ; i ++ ) scanf("%d%d%d" , &x , &y , &z) , add(x , y , z) , add(y , x , z); for(i = 1 ; i <= m ; i ++ ) { scanf("%d" , &a[i]); if(!a[i]) ans[i] = 1; } mx[0] = 1 << 30 , sum = n , getroot(1 , 0) , solve(root); for(i = 1 ; i <= m ; i ++ ) printf("%s\n" , ans[i] ? "Yes" : "No"); return 0; }