学某思趣题详解

游客观光

时间限制:1s  内存限制:128M

题目描述

H国有N个城市,它们之间用N-1条道路连接了起来,所有的城市之间刚好只有一条路径。由于H国有着举世闻名的自然风光,因此吸引了世界各地的游客前来观光。

H国有山有水,N个城市的风景各有侧重点,有些城市崇山峻岭,有些城市波光粼粼,每个城市的风景属于山和水的其中一种。

作为H国的旅游局长,小猴收到了M位游客的参观计划,第i位游客会从城市A_i到城市B_i的路径进行参观(A_i可能等于B_i)。

每位游客对自然风光有自己的喜好,有些人只喜欢看山,有些人只喜欢看水。如果游客在参观途中能看到自己喜欢的景色,他们就会感到高兴,否则就会不高兴。

请你帮小猴求出每位游客在拜访H国后会不会高兴。

【输入格式】

输入的第一行包含两个整数 N 和 M

第二行是一个长为 N 的字符串。如果第 i 个城市的景点是山,则字符串中第 i 个字符为 'M',如果第 i 个城市的景点是水则为 'W'。

接下来 N-1 行,每行包含两个不同的整数 X 和 Y1 \leq X, Y \leq N),表示城市 X 与 Y 之间有一条道路。

接下来M 行,每行包含整数 A_iB_i,以及一个字符 C_iA_i 和 B_i​ 表示游客i 参观时路径的端点,C_i 是 'M' 或 'W' 之一,表示第 ii 个朋友喜欢看山或是看水。

【输出格式】

输出一个长为 M 的二进制字符串。如果第 i 位游客会感到高兴,则字符串的第 i 个字符为 '1',否则为 '0'。

【输入输出样例#1】

输入#1

5 5
MMWMW
1 2
2 3
2 4
1 5
1 4 M
1 4 W
1 3 W
1 3 M
5 5 M

输出#1

10110

【说明提示】

【样例解释】

在这里,从城市1 到城市4 的路径包括城市 1、2 和 4。所有这些城市的景点都是山,所以第一位游客会感到满意,而第二位游客不会。

【数据范围】

对于100\%的数据,1 \leq N \leq 10^51 \leq M \leq 10^5

一看题目,哎呀,好像很简单,一看数据范围,放弃了

我们很快便能想出一种O(mn)的算法,思路是遇上一个游客,就bfs一下,再判断一下就行了。但这样很麻烦,甚至会错掉。

那么,怎么优化呢?其实还有种方法,用的是dfs,是找到一个点的周边的属性相同的点的连通块。就是找到风景相同的、中间不会遇上风景不同的城市。这样记录下来,会很方便

但,这种方法在极端数据,不,是大数据面前就会崩溃,只能得42分

此时,主角登场了

它,就是大名鼎鼎的

并查集

并查集,具有速度快、效率高、占用空间较小等优点,可以快速合并两个集合,图论的Kruskal、连通图,以及LCA,均有涉及

板子大家都会(要用上路径压缩、子树大小判断等),怎么个合并办法呢?

当两城有路且风景相同时,要合并

代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
int f[100007],size[100007],n,m,a,b;
char c;
char x[100007];
void init(int m){for(int i=1;i<=m;i++) f[i]=i,size[i]=1;}
int find(int x){return (x==f[x])?x:find(f[x]);}//仔细观察,看似短小精悍的、天衣无缝的代码里有一处巨大的漏洞,你找到了吗?
void merge(int a,int b){
	if(find(a)==find(b))
	    return;
	if(size[a]>size[b])
	    f[find(a)]=find(b),size[b]+=size[a];
	else
	    f[find(b)]=find(a),size[a]+=size[b];
}
signed main(){
	scanf("%lld%lld",&n,&m);
	init(n);
	scanf("%s",x+1);
	for(int i=1;i<n;i++){
		scanf("%lld%lld",&a,&b);
		if(x[a]==x[b])
		    merge(a,b);
	}
	while(m--){
		scanf("\n%lld %lld %c",&a,&b,&c);
		if(find(a)!=find(b))
		    putchar('1');
		else if(x[a]==c)
		    putchar('1');
		else
		    putchar('0');
	}
	return 0;
}

然而只有50分

还有什么可做的吗?

有!

问题在find函数里,递归时栈的调用会很慢,还容易爆栈,可以改为非递归,AC(就是如此神奇,看来下次少用递归了)

#include<bits/stdc++.h>
using namespace std;
#define int long long
int f[100007],size[100007],n,m,a,b;
char c;
char x[100007];
void init(int m){for(int i=1;i<=m;i++) f[i]=i,size[i]=1;}
int find(int x){
	int ans,nx=x;
	while(nx!=f[nx]){
		nx=f[nx];
	}
	while(x!=f[x]){
		int l=f[x];
		f[x]=nx;
		x=l;
	}
	return nx;
}
void merge(int a,int b){
	if(find(a)==find(b))
	    return;
	if(size[a]>size[b])
	    f[find(a)]=find(b),size[b]+=size[a];
	else
	    f[find(b)]=find(a),size[a]+=size[b];
}
signed main(){
	scanf("%lld%lld",&n,&m);
	init(n);
	scanf("%s",x+1);
	for(int i=1;i<n;i++){
		scanf("%lld%lld",&a,&b);
		if(x[a]==x[b])
		    merge(a,b);
	}
	while(m--){
		scanf("\n%lld %lld %c",&a,&b,&c);
		if(find(a)!=find(b))
		    putchar('1');
		else if(x[a]==c)
		    putchar('1');
		else
		    putchar('0');
	}
	return 0;
}
小猴编程(8375039373182185)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值