[每日随题9] 贪心 - 数学 - 并查集

整体概述

  • 难度:1400 \(\rightarrow\) 1400 \(\rightarrow\) 1800

P7228 [COCI 2015/2016 #3] MOLEKULE

  • 标签:贪心

  • 前置知识:链式前向星

  • 难度:黄 1400

题目描述:

image

输入格式:

image

image

输出格式:

image

样例输入:

3
1 2
2 3
4
2 1
1 3
4 1

样例输出:

1
0
0
1
0

解题思路:

  • 题目给定一棵树,要求将树的边添加方向,使得通路最短。

  • 那么显然我们控制所有的通路长度仅为 \(1\),我们只需要任选一个节点作根节点,第一层到第二层所有边均朝下,第二层到第三层所有边均朝上,以此类推。

  • 那么把每一条边标上方向,最后输出即可。

完整代码

#include<bits/stdc++.h>
#define Size(x) ((int)(x).size())
#define int long long
using namespace std;
const int N = 1e5+5;
int n,ha[N],idx = 1;
struct Edge{
	int from,to,ne;
	pair<int,int> res;
}edge[N<<1];
inline void ins(int u,int v){
	edge[++idx] = {u,v,ha[u]}, ha[u] = idx;
}
inline void dfs(int u,int par,int drt){
	for(int i=ha[u];i;i=edge[i].ne){
		int v = edge[i].to;
		if(v == par) continue;
		if(drt) edge[i].res = edge[i^1].res = {u,v};
		else edge[i].res = edge[i^1].res = {v,u};
		dfs(v,u,drt^1);
	}
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin >> n;
	for(int i=1,u,v;i<=n-1;i++){
		cin >> u >> v;
		ins(u,v), ins(v,u);
	}  
	dfs(1,0,1);
	for(int i=1;i<=n-1;i++) cout << (edge[i*2].res.first == edge[i*2].from) << '\n';
	return 0;
}

P10329 [UESTCPC 2024] Add

  • 标签:数学

  • 前置知识:逆元

  • 难度:黄 1400

题目描述:

image

输入格式:

image

输出格式:

image

样例输入:

3
4
2
5
3
4
3
5
3
8
1
3

样例输出:

30
5
55
30
14
55
204
1
14

解题思路:

  • 通过题目给出的三组样例,我们可以发现:
n : ans
1 : 1
2 : 5
3 : 14
4 : 30
5 : 55
8 : 204
  • 存在着很强的规律,即 \(n=i\) 时,\(ans = 1^2 + 2^2 + 3^2 + ... + i^2\),那么我们直接用平方和公式求和即可。

  • 需要注意的是,\(\frac {n·(n+1)·(2n+1)} 6\) 中的除以 \(6\) 在模意义下需要是乘以 \(6\) 的逆元。

完整代码

#include<bits/stdc++.h>
#define Size(x) ((int)(x).size())
#define int long long
using namespace std;
const int mod = 998244353;
inline int qpow(int a,int b){
	int res = 1;
	for(;b;b>>=1,a=a*a%mod)
		if(b&1) res=res*a%mod;
	return res;
}
inline void solve(){
	int n; cin >> n;
	cout << n*(n+1)%mod*(2*n+1)%mod*qpow(6,mod-2)%mod << '\n';
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int T; cin >> T;
	while(T--) solve();
	return 0;
}

P3535 [POI 2012] TOU-Tour de Byteotia

  • 标签:并查集

  • 前置知识:无

  • 难度:绿 1800

题目描述:

image

输入格式:

image

image

输出格式:

image

样例输入:

11 13 5
1 2
1 3
1 5
3 5
2 8
4 11
7 11
6 10
6 9
2 3
8 9
5 9
9 10

样例输出:

3
2 3
5 9
3 5

解题思路:

  • 题目只要求编号小于等于 \(k\) 的节点不存在环上,那么若有一条边只涉及编号大于 \(k\) 的节点,直接连上该边,不可能被删除。

  • 接下来问题就转化为一堆孤点,和若干个连通块之间的问题。我们把每个连通块都看成一个点,那么问题就变为最多可以连多少条边,使得图中无环。

  • 显然,孤立点到每个连通块都只能连一条边,那么我们可以用并查集来维护,只有两个点不在同一个连通块内的时候才可以连边,这样就保证了最后连出来的图上无环。

  • 总复杂度 \(O(m)\)

完整代码

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+5,M = 2e6+5;
int n,m,k;
struct Edge{int u,v;}edge[M];
int fa[N],siz[N];
inline int find(int x){
	return fa[x] == x ? x : fa[x] = find(fa[x]);
}
inline void merge(int u,int v){
	int x = find(u),y = find(v);
	if(x == y) return;
	if(siz[y] > siz[x]) siz[y] += siz[x],fa[x] = y;
	else siz[x] += siz[y],fa[y] = x; 
}
vector<Edge> res;
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin >> n >> m >> k;
	for(int i=1;i<=n;i++) fa[i] = i,siz[i] = 1;
	for(int i=1;i<=m;i++){
		cin >> edge[i].u >> edge[i].v;	
		if(edge[i].u > k && edge[i].v > k) merge(edge[i].u,edge[i].v);
	} 
	for(int i=1;i<=m;i++){
		if(edge[i].u > k && edge[i].v > k) continue;
		if(find(edge[i].u) == find(edge[i].v))
			res.push_back({edge[i].u,edge[i].v});
		else merge(edge[i].u,edge[i].v);
	}
	cout << res.size() << '\n';
	for(auto [u,v]:res){
		if(u > v) swap(u,v);
		cout << u << ' ' << v <<'\n';
	} 
	return 0;
}
posted @ 2025-07-19 01:18  浅叶梦缘  阅读(9)  评论(0)    收藏  举报