【bzoj3362/3363/3364/3365】[Usaco2004 Feb]树上问题杂烩 并查集/树形dp/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
1 6 13 E
6 3 9 E
3 5 7 S
4 1 3 N
2 4 20 W
4 7 2 S
3
1 6 1
1 4 3
2 6 6

样例输出T1

13
-1
10


样例输入T2

7 6

1 6 13 E

6 3 9 E

3 5 7 S

4 1 3 N

2 4 20 W

4 7 2 S

样例输出T2

52


样例输入T3

7 6

1 6 13 E

6 3 9 E

3 5 7 S

4 1 3 N

2 4 20 W

4 7 2 S

3

1 6

1 4

2 6

样例输出T3

13

3

36


样例输入T4

7 6

1 6 13 E

6 3 9 E

3 5 7 S

4 1 3 N

2 4 20 W

4 7 2 S

10

样例输出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;
}
时间: 2024-10-11 21:51:56

【bzoj3362/3363/3364/3365】[Usaco2004 Feb]树上问题杂烩 并查集/树形dp/LCA/树的点分治的相关文章

hdu 2545 树上战争(并查集)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2545 树上战争 Time Limit: 10000/4000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 564    Accepted Submission(s): 305 Problem Description 给一棵树,如果树上的某个节点被某个人占据,则它的所有儿子都被占据,

bzoj 3365 [Usaco2004 Feb]Distance Statistics 路程统计(点分治,单调)

[题意] 求树上长度不超过k的点对数目. [思路] 和 Tree 一样一样的. 就是最后统计的时候别忘把根加上. [代码] 1 #include<set> 2 #include<cmath> 3 #include<queue> 4 #include<vector> 5 #include<cstdio> 6 #include<cstring> 7 #include<iostream> 8 #include<algori

BZOJ 3365: [Usaco2004 Feb]Distance Statistics 路程统计

Description 一棵树,统计距离不大于 \(k\) 的点对个数. Sol 点分治. 发现自己快把点分治忘干净了... 找重心使所有儿子的最大值尽量小,然后每次处理全部子树,再减去每个子树的贡献,这样就得到子树间的贡献了,然后再搞子树就可以,这就是一个子问题了. Code /************************************************************** Problem: 3365 User: BeiYu Language: C++ Result

【刷题】BZOJ 3365 [Usaco2004 Feb]Distance Statistics 路程统计

Description 在得知了自己农场的完整地图后(地图形式如前三题所述),约翰又有了新的问题.他提供 一个整数K(1≤K≤109),希望你输出有多少对农场之间的距离是不超过K的. Input 第1到I+M行:与前三题相同: 第M+2行:一个整数K. Output 农场之间的距离不超过K的对数. Sample Input 7 6 1 6 13 E 6 3 9 E 3 5 7 S 4 1 3 N 2 4 20 W 4 7 2 S 10 Sample Output 5 有五对道路之间的距离小于10

bzoj 3365: [Usaco2004 Feb]Distance Statistics 路程统计【容斥原理+点分治】

统计在一个root下的两个子树,每个子树都和前面的运算一下再加进去对于这种需要排序的运算很麻烦,所以考虑先不去同子树内点对的算出合法点对个数,然后减去每一棵子树内的合法点对(它们实际上是不合法的,相当于一个容斥) 算点对用排序+双指针即可 #include<iostream> #include<cstdio> #include<algorithm> using namespace std; const int N=1000005; int n,m,h[N],cnt,rt

树的直径 【bzoj3363】[Usaco2004 Feb]Cow Marathon 奶牛马拉松

3363: [Usaco2004 Feb]Cow Marathon 奶牛马拉松 Description ? 最近美国过度肥胖非常普遍,农夫约翰为了让他的奶牛多做运动,举办了奶牛马拉松.马拉 松路线要尽量长,所以,告诉你农场的地图(该地图的描述与上题一致),请帮助约翰寻找两个 最远农场间的距离. Input ? 第1行:两个分开的整数N和M. ? 第2到M+1行:每行包括4个分开的内容,Fi,F2,L,D分别描述两个农场的编号,道路的长 度,F1到F2的方向N,E,S,W. Output ? 一个

bzoj3367[Usaco2004 Feb]The Big Game 球赛*

bzoj3367[Usaco2004 Feb]The Big Game 球赛 题意: n只奶牛,每只支持两个球队中的一个,它们依次上车,上到一定程度可以开走这辆车并换下一辆继续上.要求一辆车上支持不同球队的奶牛数的差≤I,或者这辆车上只有支持同一球队的牛.问通过安排换车时机所能得到的车数的最小值.n≤2500. 题解: f[i]=f[j]+1,abs(sum[0][i]-sum[0][j]-sum[1][i]+sum[1][j])<=I||sum[0][i]==sum[0][j]||sum[1]

BZOJ 3367: [Usaco2004 Feb]The Big Game 球赛( dp )

dp(i)表示前i个人最少坐多少辆车, dp(i) = min(dp(j) + 1, dp(i)) (0 <= j < i 且 (i, j]的人能坐在一辆车上) 时间复杂度O(n²) --------------------------------------------------------------------------- #include<bits/stdc++.h> using namespace std; const int maxn = 2509; int sum

【BZOJ4033】[HAOI2015]树上染色 树形DP

[BZOJ4033][HAOI2015]树上染色 Description 有一棵点数为N的树,树边有边权.给你一个在0~N之内的正整数K,你要在这棵树中选择K个点,将其染成黑色,并将其他的N-K个点染成白色.将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间距离的和的收益.问收益最大值是多少. Input 第一行两个整数N,K. 接下来N-1行每行三个正整数fr,to,dis,表示该树中存在一条长度为dis的边(fr,to). 输入保证所有点之间是联通的. N<=2000,0<=K&l