【bzoj3362/3363/3364/3365】[Usaco2004 Feb]树上问题杂烩 并查集/树的直径/LCA/树的点分治
题目描述
农夫约翰有N(2≤N≤40000)个农场,标号1到N,M(2≤M≤40000)条的不同的垂直或水平的道路连结着农场,道路的长度不超过1000.这些农场的分布就像下面的地图一样,
图中农场用F1..F7表示, 每个农场最多能在东西南北四个方向连结4个不同的农场.此外,农场只处在道路的两端.道路不会交叉且每对农场间有且仅有一条路径.邻居鲍伯要约翰来导航,但约翰丢了农场的地图,他只得从电脑的备份中修复了.每一条道路的信息如下:
从农场23往南经距离10到达农场17
从农场1往东经距离7到达农场17
T1给定k个询问,每次询问给定F1、F2、J,求出知道前J条信息时求出F1和F2的曼哈顿距离,根据已知信息求不出则输出-1
T2求出距离最远的两个点间的距离
T3给定k个询问,每次询问给定x、y,求x、y间距离
T4给定k,求出满足x到y的距离小于等于k的无序点对(x,y)的个数
输入
第1行:两个分开的整数N和M.
第2到M+1行:每行包括4个分开的内容,F1,F2,三,D分别描述两个农场的编号,道路的长度,F1到F2的方向N,E,S,W.
对于T1:第M+2行:一个整数,K(1≤K≤10000),表示问题个数.
第M+3到M+K+2行:每行表示一个问题,由3部分组成:F1,F2,,.其中F1和F2表示两个被问及的农场.而J(1≤J≤M)表示问题提出的时刻.J为1时,表示得知信息1但未得知信息2时.
对于T3:第2+M行:一个整数K(1≤K≤10000).
第3+M到2+M+K行:每行输入2个整数,代表两个农场.
对于T4:第M+2行:一个整数K.
输出
T1:第1到K行:每行一个整数,回答问题.表示两个农场间的曼哈顿距离.不得而知则输出-1.
T2:一个整数,表示最远两个衣场间的距离.
T3:对每个问题,输出单独的一个整数,给出正确的距离.
T4:农场之间的距离不超过K的对数.
样例输入T1 7 6 样例输出T1 13 |
样例输入T2 7 6 样例输出T2 52 |
样例输入T3 7 6 样例输出T3 13 |
样例输入T4 7 6 样例输出T4 5 |
题解
各种树上问题处理方法
题目中描述“每对农场间有且仅有一条路径”,所以这个图其实就是一棵树,也就是说m是没用的。
另外路径方向除了T1都没用。。。
T1:将询问离线,按时间排序,用带权并查集维护每个点的祖先以及该点到祖先的水平距离和竖直距离即可。
#include <cstdio> #include <cmath> #include <algorithm> #define N 40010 using namespace std; struct data { int x , y , p , pos; }q[10010]; int a[N] , b[N] , cx[N] , cy[N] , f[N] , dx[N] , dy[N] , ans[10010]; char str[5]; bool cmp(data a , data b) { return a.p < b.p; } int find(int x) { if(x == f[x]) return x; int t = f[x]; f[x] = find(t); dx[x] += dx[t]; dy[x] += dy[t]; return f[x]; } int main() { int n , i , t , m , ta , tb; scanf("%d%*d" , &n); for(i = 1 ; i < n ; i ++ ) { scanf("%d%d%d%s" , &a[i] , &b[i] , &t , str); if(str[0] == 'E') cx[i] = t; if(str[0] == 'S') cy[i] = t; if(str[0] == 'W') cx[i] = -t; if(str[0] == 'N') cy[i] = -t; } scanf("%d" , &m); for(i = 1 ; i <= m ; i ++ ) scanf("%d%d%d" , &q[i].x , &q[i].y , &q[i].p) , q[i].pos = i;; sort(q + 1 , q + m + 1 , cmp); for(i = 1 ; i <= n ; i ++ ) f[i] = i; for(t = i = 1 ; i <= m ; i ++ ) { while(t <= q[i].p) { ta = find(a[t]) , tb = find(b[t]); if(ta != tb) f[ta] = tb , dx[ta] = dx[b[t]] + cx[t] - dx[a[t]] , dy[ta] = dy[b[t]] + cy[t] - dy[a[t]]; t ++ ; } ta = find(q[i].x) , tb = find(q[i].y); ans[q[i].pos] = (ta == tb ? abs(dx[q[i].x] - dx[q[i].y]) + abs(dy[q[i].x] - dy[q[i].y]) : -1); } for(i = 1 ; i <= m ; i ++ ) printf("%d\n" , ans[i]); return 0; }
T2:求树的直径可以使用一遍树形dp,但这里懒了直接两遍dp求deep,即先找到任意一个deep最大的点,再以这个点为根求出直径。
#include <cstdio> #include <cstring> #define N 40010 int head[N] , to[N << 1] , len[N << 1] , next[N << 1] , cnt , deep[N]; void add(int x , int y , int z) { to[++cnt] = y , len[cnt] = z , next[cnt] = head[x] , head[x] = cnt; } void dfs(int x , int fa) { int i; for(i = head[x] ; i ; i = next[i]) if(to[i] != fa) deep[to[i]] = deep[x] + len[i] , dfs(to[i] , x); } int main() { int n , i , x , y , z , k; scanf("%d%*d" , &n); for(i = 1 ; i < n ; i ++ ) scanf("%d%d%d%*s" , &x , &y , &z) , add(x , y , z) , add(y , x , z); dfs(1 , 0); for(k = 0 , i = 1 ; i <= n ; i ++ ) if(deep[i] > deep[k]) k = i; memset(deep , 0 , sizeof(deep)) , dfs(k , 0); for(k = 0 , i = 1 ; i <= n ; i ++ ) if(deep[i] > k) k = deep[i]; printf("%d\n" , k); return 0; }
T3:倍增LCA模板题,注意细节。
#include <cstdio> #include <algorithm> #define N 40010 using namespace std; int head[N] , to[N << 1] , len[N << 1] , next[N << 1] , cnt , fa[N][18] , deep[N] , dis[N][18] , log[N]; void add(int x , int y , int z) { to[++cnt] = y , len[cnt] = z , next[cnt] = head[x] , head[x] = cnt; } void dfs(int x) { int i; for(i = 1 ; i <= log[deep[x]] ; i ++ ) fa[x][i] = fa[fa[x][i - 1]][i - 1] , dis[x][i] = dis[x][i - 1] + dis[fa[x][i - 1]][i - 1]; for(i = head[x] ; i ; i = next[i]) if(to[i] != fa[x][0]) fa[to[i]][0] = x , dis[to[i]][0] = len[i] , deep[to[i]] = deep[x] + 1 , dfs(to[i]); } int query(int x , int y) { int i , ans = 0; if(deep[x] <= deep[y]) swap(x , y); for(i = log[deep[x] - deep[y]] ; ~i ; i -- ) if(deep[x] - (1 << i) >= deep[y]) ans += dis[x][i] , x = fa[x][i]; if(x == y) return ans; for(i = log[deep[x]] ; ~i ; i -- ) if(fa[x][i] != fa[y][i]) ans += dis[x][i] + dis[y][i] , x = fa[x][i] , y = fa[y][i]; return ans + dis[x][0] + dis[y][0]; } int main() { int n , i , x , y , z , k; scanf("%d%*d" , &n); for(i = 1 ; i < n ; i ++ ) scanf("%d%d%d%*s" , &x , &y , &z) , add(x , y , z) , add(y , x , z); for(i = 2 ; i <= n ; i ++ ) log[i] = log[i >> 1] + 1; dfs(1); scanf("%d" , &k); while(k -- ) scanf("%d%d" , &x , &y) , printf("%d\n" , query(x , y)); return 0; }
T4:树的点分治,几乎同 poj1741 ,还是单组测试数据,直接无视掉m和方向之后按照那道题做就好了。
#include <cstdio> #include <cstring> #include <algorithm> #define N 40010 using namespace std; int m , head[N] , to[N << 1] , len[N << 1] , next[N << 1] , cnt , si[N] , deep[N] , root , vis[N] , f[N] , sn , d[N] , tot , ans; 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) { f[x] = 0 , si[x] = 1; int i; for(i = head[x] ; i ; i = next[i]) if(to[i] != fa && !vis[to[i]]) getroot(to[i] , x) , si[x] += si[to[i]] , f[x] = max(f[x] , si[to[i]]); f[x] = max(f[x] , sn - si[x]); if(f[root] > f[x]) root = x; } void getdeep(int x , int fa) { d[++tot] = deep[x]; int i; for(i = head[x] ; i ; i = next[i]) if(to[i] != fa && !vis[to[i]]) deep[to[i]] = deep[x] + len[i] , getdeep(to[i] , x); } int calc(int x) { tot = 0 , getdeep(x , 0) , sort(d + 1 , d + tot + 1); int i = 1 , j = tot , sum = 0; while(i < j) { if(d[i] + d[j] <= m) sum += j - i , i ++ ; else j -- ; } return sum; } void dfs(int x) { deep[x] = 0 , vis[x] = 1 , ans += calc(x); int i; for(i = head[x] ; i ; i = next[i]) if(!vis[to[i]]) deep[to[i]] = len[i] , ans -= calc(to[i]) , sn = si[to[i]] , root = 0 , getroot(to[i] , 0) , dfs(root); } int main() { int n , i , x , y , z; scanf("%d%*d" , &n); for(i = 1 ; i < n ; i ++ ) scanf("%d%d%d%*s" , &x , &y , &z) , add(x , y , z) , add(y , x , z); scanf("%d" , &m); f[0] = 0x7fffffff , sn = n; getroot(1 , 0) , dfs(root); printf("%d\n" , ans); return 0; }