Codeforces Round #805 (Div. 3) - E, F, G

本文探讨了F.EquateMultisets中的集合操作问题,通过比较和转换操作1和2,分析如何判断两个multiset是否可以通过有限次操作达成一致,并给出了代码实现。同时,涉及了PassablePaths中的树结构操作和SplitIntoTwoSets的二元组分组问题,展示了如何利用并查集和染色法解决。
摘要由CSDN通过智能技术生成

F. Equate Multisets

题意
给出两个 multiset A A A B B B,判断能否将集合 B B B 经过若干次操作变为集合 A A A?如果可以,最少操作多少次?

  • 操作1:选择一个元素 x x x,将其变为 x ∗ 2 x*2 x2
  • 操作2:选择一个元素 x x x,将其变为 ⌊ x 2 ⌋ \lfloor \frac{x}{2} \rfloor 2x

思路
假设集合 A 中的一个偶数元素为 x,其对应在集合 B 中的元素为 x/2,那么元素 x 除以2 就相当于元素 x/2 乘以 2,仍然是相互匹配的。
那么操作就可以转化为:

  • 操作1:选择 A 中的一个偶数元素 x x x,将其变为 x / 2 x/2 x/2
  • 操作2:选择 B 中的一个元素 x x x,将其变为 ⌊ x 2 ⌋ \lfloor \frac{x}{2} \rfloor 2x

问,能否将两个集合变为相同?

因为每次操作都会使元素变小,所以每次取出两个集合中的最大元素,判断这两个元素是否相同。

  • 如果相同,将这两个元素从集合中删掉;
  • 如果不同,把较大的元素除以2后重新放入,操作次数++。(如果是 A 集合元素较大要除2,要保证其为偶数;否则其一定不会有对应元素,直接 -1)
  • 最终判断集合是否为空。

注意
其实只要判断能否将两个集合变为相同,只需要判断是否 A 集合中所有元素的奇因子是否匹配就可以了。
A 集合中元素的偶因子可以由乘2得到,关键在于奇因子如何得到:只能由 B 集合元素除 2 得到。所以把 AB 集合的所有元素都变为奇因子,将 B 集合奇因子不断除 2 寻找匹配。

Code

#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)

const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N];

signed main(){
	Ios;
	cin >> T;
	while(T--)
	{
		cin >> n;
		
		priority_queue<int> que1, que2;
		
		for(int i=1;i<=n;i++)
		{
			int x; cin >> x;
			while(x % 2 == 0) x /= 2;
			que1.push(x);
		}
		
		for(int i=1;i<=n;i++)
		{
			int x; cin >> x;
			que2.push(x);
		}
		
		int flag = 0;
		while(que1.size())
		{
			int x = que1.top(); que1.pop();
			int y = que2.top(); que2.pop();
			
			if(x == y) continue;
			y /= 2;
			if(y == 0){
				flag = 1;
				break;
			}
			que1.push(x), que2.push(y);
		}
		if(flag) cout << "NO\n";
		else cout << "YES\n";
	}
	
	return 0;
}

Ex - Multiply or Divide by 2

#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define endl '\n'

const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N];

signed main(){
	Ios;
	
	priority_queue<int> que1, que2;
	
	cin >> n;
	for(int i=1;i<=n;i++)
	{
		int x; cin >> x;
		que1.push(x);
	}
	for(int i=1;i<=n;i++)
	{
		int x; cin >> x;
		que2.push(x);
	}
	
	int cnt = 0, flag = 0;
	while(que1.size())
	{
		int x = que1.top(); que1.pop();
		int y = que2.top(); que2.pop();
		
		if(x == y) continue;
		if(x > y) x /= 2, cnt ++;
		else if(x < y)
		{
			if(y % 2 == 0) y /= 2, cnt ++;
			else{
				flag = 1;
				break;
			}
		}
		que1.push(x);
		que2.push(y);
	}
	if(flag){
		cout << -1 << endl;
	}
	else cout << cnt << endl;
	
	return 0;
}

G. Passable Paths

题意
对于 n 节点的一棵树,一共 q 次询问。
每次询问,给定 m 个数的集合,判断集合中的所有元素是否在同一条链中?

1 ≤ n ≤ 2 ⋅ 1 0 5 ,   1 ≤ m ≤ n ,   1 ≤ p i ≤ n 1 \le n \le 2 \cdot 10^5,\ 1 \le m \le n, \ 1 \le p_i \le n 1n2105, 1mn, 1pin

easy version: 1 ≤ q ≤ 5 1 \le q \le 5 1q5
hard version: 1 ≤ q ≤ 1 0 5 1 \le q \le 10^5 1q105

思路
easy version:
直接 dfs 搜整棵树判断即可,关键在于从哪个点开始搜。
从所有元素所在链的端点开始,即任意选定根节点确定每个节点深度后,深度最大的元素。
搜的时候记录该路径中在集合中的节点个数,如果在当前路径中集合节点个数为集合大小,说明集合中的所有节点都在当前路径中,那么所有元素在同一条链。

hard version:
先假设确实存在这条链,再判断是否所有点在这条链中。
如果所有元素确实在同一条链上的话,那么其一个端点 l 是深度最大的元素(已确定),另一个端点 r 是端点 l 所在路径之外的深度最大的元素。

  • 遍历所有节点,对于节点 x,如果 lca(x, l) 不为 x 的话,说明其和端点 l 不在同一路径上,取深度最大的那个作为端点 r。
  • 如果说 r 未找到,那么说明所有元素都在同一路径上,直接 YES;

两个端点确定了,lca(l, r) 到这两个端点的两端路径相连接便是整条链,判断其他点是否在这条链上。

  • 对于一个节点 x,如果其和一个端点的 lca 为 x,和另一个端点的 lca 为 lca(l, r) 的话,就说明这个节点在该链上。

Code
easy:

#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)

const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N];
vector<int> e[N];
int flag, ans;
int dep[N];
int cnt, pre[N];
int k;

void dfs1(int x, int fa)
{
	for(int tx : e[x])
	{
		if(tx == fa) continue;
		dep[tx] = dep[x] + 1;
		dfs1(tx, x);
	}
}

void dfs(int x, int fa, int cnt) //第三个变量始终表示一条链上满足点的个数 
{
	if(cnt == k){
		ans = 1;
		return;
	}
	
	for(int tx : e[x])
	{
		if(tx == fa) continue;
		
		int t = 0;
		if(mp[tx]) t = 1;
		dfs(tx, x, cnt+t);
	}
}

signed main(){
	Ios;
	cin >> n;
	for(int i=1;i<n;i++)
	{
		int x, y; cin >> x >> y;
		e[x].push_back(y);
		e[y].push_back(x);
	}
	
	dfs1(1, 0);
	
	cin >> m;
	while(m--)
	{
		cin >> k;
		mp.clear();
		
		int mina = -1, root = 1;
		for(int i=1;i<=k;i++)
		{
			int x; cin >> x;
			if(dep[x] > mina){
				mina = dep[x];
				root = x;
			}
			mp[x] = 1;
		}
		
		ans = 0;
		dfs(root, 0, 1);
		
		if(ans) cout << "YES\n";
		else cout << "NO\n";
	}
	
	return 0;
}

hard:

#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)

const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N];
int f[N][30], k, dep[N];
vector<int> e[N];

void bfs()
{
	dep[0] = 0, dep[1] = 1;
	queue<int> que;
	que.push(1);
	
	while(que.size())
	{
		int x = que.front(); que.pop();
		
		for(int tx : e[x])
		{
			if(tx == f[x][0]) continue;
			
			dep[tx] = dep[x] + 1;
			que.push(tx);
			
			f[tx][0] = x;
			for(int i=1;i<=k;i++)
				f[tx][i] = f[f[tx][i-1]][i-1];
		}
	}
}

int lca(int x, int y)
{
	if(dep[x] < dep[y]) swap(x, y);
	
	for(int i=k;i>=0;i--)
	{
		if(dep[f[x][i]] >= dep[y]) x = f[x][i];
	}
	
	if(x == y) return x;
	
	for(int i=k;i>=0;i--)
	{
		if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
	}
	return f[x][0];
}

signed main(){
	Ios;
	cin >> n;
	
	k = log(n)/log(2);
	
	for(int i=1;i<n;i++)
	{
		int x, y; cin >> x >> y;
		e[x].push_back(y);
		e[y].push_back(x);
	}
	
	bfs();
	
	cin >> m;
	while(m--)
	{
		int q; cin >> q;
		
		int l = 0, r = 0;
		int maxa = 0;
		for(int i=1;i<=q;i++){
			cin >> a[i];
			if(dep[a[i]] > maxa){
				maxa = dep[a[i]];
				l = a[i];
			}
		}
		
		maxa = 0;
		for(int i=1;i<=q;i++)
		{
			int x = a[i];
			if(l == x) continue;
			if(lca(l, x) != x){
				if(dep[x] > maxa){
					maxa = dep[x];
					r = x;
				}
			}
		}
		
		if(!r){
			cout << "YES\n";
			continue;
		}
		
		int mid = lca(l, r);
		
		int flag = 0;
		for(int i=1;i<=q;i++)
		{
			int x = a[i];
			if(x == l || x == r) continue;
			int m1 = lca(x, l), m2 = lca(x, r);
			if(m1 == x && m2 == mid || m2 == x && m1 == mid) continue;
			flag = 1;
		}
		if(flag) cout << "NO\n";
		else cout << "YES\n";
	}
	
	return 0;
}

E. Split Into Two Sets

题意
给定 n 个二元组,每个二元组有两个元素{x, y}。
问,是否能将所有二元组分成 A,B 两部分,每个二元组必须属于其中一个。
每个部分要满足:其中所有元素不得有重复。

2 ≤ n ≤ 2 ⋅ 1 0 5 ,   n 为 偶 数 2 \le n \le 2 \cdot 10^5, \ n为偶数 2n2105, n
1 ≤ a i , b i ≤ n 1 \le a_i, b_i \le n 1ai,bin

思路
方法1:
场上想的做法是这样的,对于一个二元组 {x, y} 来说,如果 x 出现在另一个二元组,y 也出现在另一个二元组,那么两个二元组一定要放在同一部分里,才能保证二元组{x, y} 能够放到另一部分,才能满足要求。
所以将这样的两两二元组用并查集合并起来,最终判断是否一个集合中存在相同的元素。
方法2:
染色判定二分图
将所有的二元组分成两部分实现不冲突,其实就是相当于,将每个二元组看成一个节点,将不能放在一起的二元组两两连边,判断是否能构成二分图。
对于一个元素出现在两个二元组中,那么这两个二元组就不能放在同一部分,那么就将这两个节点连边。
然后染色判定二分图即可。

Code
方法1:

#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)
unordered_map<int,int> mp1, mp2;

const int N = 200010, mod = 1e9+7;
int T, n, m;
PII a[N];
int pre[N];

vector<int> v[N];

int find(int x){
	if(pre[x] != x) pre[x] = find(pre[x]);
	return pre[x];
}

signed main(){
	Ios;
	cin >> T;
	while(T--)
	{
		cin >> n;
		
		mp1.clear();
		mp2.clear();
		
		int flag = 0;
		for(int i=1;i<=n;i++)
		{
			cin >> a[i].fi >> a[i].se;
			if(a[i].fi == a[i].se) flag = 1;
			if(mp1[a[i].fi]){
				if(mp2[a[i].fi]) flag = 1;
				else mp2[a[i].fi] = i;
			}else mp1[a[i].fi] = i;
			
			if(mp1[a[i].se]){
				if(mp2[a[i].se]) flag = 1;
				else mp2[a[i].se] = i;
			}else mp1[a[i].se] = i;
		}
		
		for(int i=1;i<=n;i++) pre[i] = i;
		
		for(int i=1;i<=n;i++)
		{
			if(mp2[a[i].fi] && mp2[a[i].se])
			{
				int x, y;
				if(i==mp1[a[i].fi]) x = mp2[a[i].fi];
				else x = mp1[a[i].fi];
				if(i==mp1[a[i].se]) y = mp2[a[i].se];
				else y = mp1[a[i].se];
				
				pre[find(x)] = find(y);
			}
		}
		for(int i=1;i<=n;i++) v[i].clear();
		
		for(int i=1;i<=n;i++) v[find(i)].push_back(i);
		
		for(int i=1;i<=n;i++)
		{
			vector<int> vt;
			for(int j : v[i]) vt.push_back(a[j].fi), vt.push_back(a[j].se);
			sort(vt.begin(), vt.end());
			for(int j=1;j<vt.size();j++) if(vt[j] == vt[j-1]) flag = 1;
		}
		
		if(flag) cout << "NO\n";
		else cout << "YES\n";
	}
	
	return 0;
}

方法2:

#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)

const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N];
vector<int> v[N], e[N];
int col[N], flag;

void dfs(int x)
{
	for(int tx : e[x])
	{
		if(col[tx] == col[x]){
			flag = 1;
			return;
		}
		if(col[tx] != -1) continue;
		
		col[tx] = col[x] ^ 1;
		dfs(tx);
	}
}

signed main(){
	Ios;
	cin >> T;
	while(T--)
	{
		cin >> n;
		for(int i=1;i<=n;i++) v[i].clear(), e[i].clear();
		
		for(int i=1;i<=n;i++)
		{
			int x, y; cin >> x >> y;
			v[x].push_back(i);
			v[y].push_back(i);
		}
		for(int i=1;i<=n;i++)
		{
			if(v[i].size() != 2) continue;
			e[v[i][0]].push_back(v[i][1]);
			e[v[i][1]].push_back(v[i][0]);
		}
		
		flag = 0;
		for(int i=1;i<=n;i++) col[i] = -1;
		for(int i=1;i<=n;i++)
		{
			if(col[i] == -1) col[i] = 0, dfs(i);
		}
		if(flag) cout << "NO\n";
		else cout << "YES\n";
	}
	
	return 0;
}

C 题因为 unordered_map 被 hack 了。。


第一次补完一场 cf 的所有题!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值