atcoder beginer contest 295 D,G题解

本文介绍了如何使用C++解决atcoder Beginner Contest 295的D题(Three Days Ago)和G题(Minimum Reachable City)。D题关键在于判断字符串子段中字符出现次数是否均为偶数,利用二进制位表示状态进行求解。G题则涉及有向图的最小可达顶点问题,采用并查集的数据结构来解答。

three days ago

D - Three Days Ago

题目大意:当一个由数字组成的字符串可以重新排列成(或已经是)某个字符串的重复两次时,它被认为是快乐的。您将获得一个由数字组成的字符串。查找满足以下所有条件的整数对数。

我们发现只要在某一个子段中,每种字符出现的次数都为偶数时,这个子段便是满足条件的。

那么,问题来了,怎么判断某一子段中各个字符出现的次数是不是偶数呢?

这里我们可以这样处理:我们对每一个字符出现的次数,它要么是奇数,要么是偶数,那么我们可以用一个二进制位表示该字符的状态,0表示出现次数为偶数,1表示出现次数为奇数,10位即可表示所有字符出现次数。这样一来,我们只需要10个二进制位就可以表示0-9各个数字的状态,开一个1<<10大小的数组就可以记录所有状态了。每一个状态转变只需要上一个状态state^(1<<(s[i]-'0'))即可。

例如20230322,我们记录一下前i个数所代表的状态:

i=0 0000000000

i=1 0000000100

i=2 0000000101

i=3 0000000001

i=4 0000001001

i=5 0000001000

i=6 0000000000

i=7 0000000100

i=8 0000000000

当两个状态相等时,则它们之间的部分恰好满足条件,那么我们只需要不断遍历字符串,把每个状态出现的次数记下来,并且每次让ans加上这个状态出现的次数即可。例如某个状态已经出现过3次,那第四次出现时,就先加ans+3,再让次数times+1;即该位置与前面三个位置分别构成3个符合条件的子串。

代码如下:

#include <bits/stdc++.h>
using namespace std;
const int maxn=5e5+10;
char s[maxn];
int t[1<<10]={1},tmp;
int main(){
	string s;
	cin >> s;
	long long ans=0;
	for(int i=0;i<s.size();i++){
		tmp^=1<<(s[i]-'0');
		ans+=t[tmp]++;
	}
	cout << ans << endl;
	return 0;
}

Minimum Reachable City

G - Minimum Reachable City

我们有一个有向图,顶点编号为1~n 。 它有n-1条边,第i条边从pi到i+1。​

有两种类型的查询,如下所示。

  • 1 u v:添加一个从顶点到顶点的边。 
  • 2 x:打印可从顶点通过某些边(包括顶点)到达的顶点的最小顶点数。

并查集

那么我们可以设立一个数组存储每一个结点的祖先,并把它设为该点所能到达的最小点,可以联想到用并查集的思想,把拥有相同祖先的点收到一个集合里。

首先读取n-1个数据,将他们存为对应点的父节点,存到一个fa数组里,之后每读入1 u v,将两个点以及他们的父节点所在的集合并起来,并且找到他们所到达的最小点设为祖先,之后2询问时,只需要输出find(x)即可。

代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
int f[maxn],fa[maxn];
int find(int x){
	return f[x]==x?x:f[x]=find(f[x]);
}
void merge(int a,int b){//合并两个点所在的集合
	int u=find(a),v=find(b);
	if(u<v) f[v]=u;
	else f[u]=v;
}
int main(){
	int n;
	cin >> n;
	for(int i=1;i<=n;i++){
		f[i]=i;
	}
	for(int i=2;i<=n;i++){
		int d;
		cin >> d;
		fa[i]=d;
	}
	int q;
    cin >> q;
	while(q--){
		int op;
		cin >> op;
		if(op==1){
			int a,b;
			cin >> a >> b;
			int u=find(a);
			while(u>b) {//向上找父亲,并找到它们对应的可到达的最小点
				f[u]=find(fa[u]);
				u=find(u);
			}
		}
		else {
			int t;
			cin >> t;
			cout << find(t) << endl;
		}
	}
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值