2026-01-19~20 hetao1733837 的刷题笔记

2026-01-19~20 hetao1733837 的刷题笔记

01-19

LGP4768 [NOI2018] 归程

原题链接:[NOI2018] 归程

分析

感觉 INF 天前就要写这题了,一直没写......
发现自己没看完题面

好像是利用了 Kruskal ⁡ \operatorname{Kruskal} Kruskal 重构树的一些神奇小性质......我已经十五分钟愣神了......似乎先考虑离线是容易的。

那么,我似乎可以一点一点往里面加一些边......似乎是对的......

然后上升到在线......

我们使用 Dijkstra ⁡ \operatorname{Dijkstra} Dijkstra 预处理所有点到起点的最短距离,问题转化为:只保留海拔大于 p p p 的边,问 v v v 所在连通分量的点权的最小值。与连通分量有关......然后就想到 Kruskal ⁡ \operatorname{Kruskal} Kruskal 重构树了?

怎么说呢?从 HT 那个 PPT 去理解, MST ⁡ \operatorname{MST} MST 似乎进行了一些妥协,使得边权尽可能小了......

那么,我们建出来 Kruskal 重构树之后,问题转化为子树最小值?哦?

那么,一个 dfs 再加个倍增完美解决。

我似乎不太会啊......

所以,这是个拼板子?

正解

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 400005, M = 800005;
int T;
int n, m, q, k, s;
struct node1{
	int u, v, l, a;
}e[M], p[M];
bool cmp(node1 a, node1 b){
	return a.a > b.a;
}
struct node2{
	int v, nxt;
}tr[M];
struct node3{
	int v, nxt, w;
}t[M];
struct node4{
	int u, w;
};
bool operator<(node4 a, node4 b){
	return a.w > b.w;
}
int tot1, tot2;
int head1[N], head2[N];
void add1(int u, int v){
	tr[++tot1].v = v;
	tr[tot1].nxt = head1[u];
	head1[u] = tot1;
}
void add2(int u, int v, int w){
	t[++tot2].v = v;
	t[tot2].nxt = head2[u];
	t[tot2].w = w;
	head2[u] = tot2;
}
int d[N];
bool vis[N];
void dijkstra(int S){
	memset(vis, 0, sizeof(vis));
	memset(d, 0x3f, sizeof(d));
	priority_queue<node4> q;
	d[S] = 0;
	q.push({S, d[S]});
	while (!q.empty()){
		auto tmp = q.top();
		q.pop();
		if (vis[tmp.u])
			continue;
		for (int i = head2[tmp.u]; i; i = t[i].nxt){
			int v = t[i].v;
			if (vis[v])
				continue;
			if (d[v] > d[tmp.u] + t[i].w){
				d[v] = d[tmp.u] + t[i].w;
				q.push({v, d[v]});
			}
		}
	}
	for (int i = 1; i <= n; i++){
		p[i].l = d[i];
	}
}
int fa[N];
int find(int x){
	return x == fa[x] ? x : fa[x] = find(fa[x]);
}
int de[N], f[N][21];
void dfs(int u, int pa){
	de[u] = de[pa] + 1;
	f[u][0] = pa;
	for (int i = 1; i <= 19; i++)
		f[u][i] = f[f[u][i - 1]][i - 1];
	for (int i = head1[u]; i; i = tr[i].nxt){
		int v = tr[i].v;
		dfs(v, u);
		p[u].l = min(p[u].l, p[v].l);
	}
}
int getmn(int x, int y){
	for (int i = 19; i >= 0; i--){
		if (de[x] - (1 << i) >0 && p[f[x][i]].a > y)
			x = f[x][i];
	}
	return p[x].l;
}
int ans = 0;
void Kruskal(){
	int cnt1 = 0, cnt2 = n;
	for (int i = 1; i <= (n << 1); i++){
		fa[i] = i;
	}
	sort(e + 1, e + m + 1, cmp);
	for (int i = 1; i <= m; i++){
		int u = e[i].u, v = e[i].v;
		int fu = find(u), fv = find(v);
		if (fu != fv){
			add1(++cnt2, fu);
			add1(cnt2, fv);
			fa[fu] = cnt2;
			fa[fv] = cnt2;
			p[cnt2].a = e[i].a;
			cnt1++;
		}
		if (cnt1 == n - 1){
			break;
		}
	}
	dfs(cnt2, 0);
	while (q--){
		int v0, p0;
		cin >> v0 >> p0;
		int x = (k * ans + v0 - 1) % n + 1;
		int y = (k * ans + p0) % (s + 1);
		ans = getmn(x, y);
		cout << ans << '\n';
	}
}
void init(){
	memset(e, 0, sizeof(e));
	memset(head1, 0, sizeof(head1));
	memset(head2, 0, sizeof(head2));
	memset(f, 0, sizeof(f));
	tot1 = 0;
	tot2 = 0;
	ans = 0;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> T;
	while (T--){
		init();
		cin >> n >> m;
		for (int i = 1; i <= m; i++){
			cin >> e[i].u >> e[i].v >> e[i].l >> e[i].a;
			add2(e[i].u, e[i].v, e[i].l);
			add2(e[i].v, e[i].u, e[i].l);
		}
		for (int i = n + 1; i <= (n << 1); i++){
			p[i].l = 0x3f3f3f3f;
		}
		dijkstra(1);
		cin >> q >> k >> s;
		Kruskal();
	}
}

01-20

CF891C Envy

原题链接1:CF891C Envy

原题链接2:C. Envy

分析

啊?咋是最小生成树的不唯一性?这咋做?

考虑暴力,每次强制先选这几条边,那么,复杂度 O ( q n l o g 2 n ) O(qnlog_2n) O(qnlog2n)。

考虑离线?先求出剩余连通块的 M S T MST MST,不断加边?那似乎也不是很优吧......

难道是 Kruskal ⁡ \operatorname{Kruskal} Kruskal 重构树上的距离?那能评......蓝......似乎是比较对的。

假了......

我们知道,在最小生成树中边权小于某个阈值 w w w 的边形成的连通分量是确定的。

那么,问题转化为:对于若干边权同为 w w w 的边,是否能同时成功合成对应连通分量。使用可撤销并查集即可。

正解

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 500005;
int n, m, q, cur;
int u[N], v[N], w[N], fa[N], sz[N], inp[N], ans[N];
vector<int> ask[N];
int top;
struct node1{
	int u, v, w;
}e[N];
struct node2{
	int fu, fv;
}un[N];
bool cmp1(node1 x, node1 y){
	return x.w < y.w;
}
bool cmp2(int x, int y){
	return w[x] < w[y];
}
bool cmp3(const vector<int> &x, const vector<int> &y){
	return w[x[1]] < w[y[1]];
}
int find(int x){
	while (x != fa[x]) x = fa[x];
	return x;
}
void merge(int x, int y){
	int fx = find(x), fy = find(y);
	if (fx == fy)
		return;
	if (sz[fx] > sz[fy])
		swap(fx, fy);
	un[++top] = {fx, fy};
	sz[fy] += sz[fx];
	fa[fx] = fy;
}
void rollback(int target){
	while (top > target){
		int fx = un[top].fu, fy = un[top].fv;
		fa[fx] = fx;
		sz[fy] -= sz[fx];
		top--;
	}
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n >> m;
	for (int i = 1; i <= n; i++){
		fa[i] = i;
		sz[i] = 1;
	}
	for (int i = 1; i <= m; i++){
		cin >> u[i] >> v[i] >> w[i];
		e[i] = {u[i], v[i], w[i]};
	}
	sort(e + 1, e + m + 1, cmp1);
	cin >> q;
	int tot = 0;
	for (int cs = 1; cs <= q; cs++){
		int k;
		cin >> k;
		ans[cs] = 1;
		for (int i = 1; i <= k; i++){
			cin >> inp[i];
		}
		sort(inp + 1, inp + k + 1, cmp2);
		for (int i = 1; i <= k; i++){
			if (i == 1 || w[inp[i]] != w[inp[i-1]]){
				ask[++tot].clear();
				ask[tot].push_back(cs);
			}
			ask[tot].push_back(inp[i]);
		}
	}
	cur = 1; 
	sort(ask + 1, ask + tot + 1, cmp3);
	for (int i = 1; i <= tot; i++){
		bool flag = true;
		while (cur <= m && e[cur].w < w[ask[i][1]]){
			merge(e[cur].u, e[cur].v);
			cur++;
		}
		int old_top = top;
		int sz_vec = ask[i].size();
		for (int j = 1; j < sz_vec; j++){
			int id = ask[i][j];
			if (find(u[id]) == find(v[id])){
				flag = false;
			}
			merge(u[id], v[id]);
		}
		rollback(old_top);
		ans[ask[i][0]] &= flag;
	}
	while (cur <= m){
		merge(e[cur].u, e[cur].v);
		cur++;
	}
	for (int i = 1; i <= q; i++){
		cout << (ans[i] ? "YES" : "NO") << '\n';
	}
}

难道被卡常了?不太清楚。

LGP5631 最小mex生成树

原题链接:最小mex生成树

分析

似乎,我枚举 w w w 这一维,钦定生成树的 mex ⁡ \operatorname{mex} mex 似乎是可行的。那怎么判有解?用并查集暴力?时间爆炸!

那怎么办?看一眼 Hint ......

不用并查集,我们把图中所有边权为 w w w 的边都删了,图依然联通不就行了!

然后,为啥这里可以想到分治?因为这里相当于删掉了图的一部分......对权值区间分治,维护区间外的可撤销并查集即可。

啊?

正解

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1000005, M = 2000005;
int n, m;
struct node {
	int u, v, w;
} e[M];
bool cmp(node x, node y){
	return x.w < y.w;
}
int fa[N], sz[N];
int find(int x){
	return x == fa[x] ? x : find(fa[x]);
}
int merge(int x, int y){
	int fx = find(x), fy = find(y);
	if (fx == fy){ 
		return 0;
	}
	if (sz[fx] > sz[fy]){
		fa[fy] = fx;
		sz[fx] += sz[fy];
		return fy;
	} 
	else{
		fa[fx] = fy;
		sz[fy] += sz[fx];
		return fx;
	}
}
void del(int x) {
	sz[fa[x]] -= sz[x];
	fa[x] = x;
}
void work(int l, int r, int pos){
	if (l == r) {
		if (sz[find(1)] == n){
			cout << l;
			exit(0);
		}
		return ;
	}
	int mid = (l + r) >> 1;
	int tmp = pos;
	int lst = 0;
	vector<int> k;
	while (e[pos].w <= r && pos <= m){
		if (e[pos].w > mid){
			lst = merge(e[pos].u, e[pos].v);
			if (lst) k.push_back(lst);
		}
		pos++;
	}
	work(l, mid, tmp);
	for (int i = k.size() - 1; i >= 0; i--){
		del(k[i]);
	}
	k.clear();
	pos = tmp;
	while (e[pos].w <= mid && pos <= m){
		lst = merge(e[pos].u, e[pos].v);
		if (lst) 
			k.push_back(lst);
		pos++;
	}
	work(mid + 1, r, pos); 
	for (int i = k.size() - 1; i >= 0; i--){
		del(k[i]);
	}
	k.clear();
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> m;
	for (int i = 1; i <= m; i++){
		cin >> e[i].u >> e[i].v >> e[i].w;
	}
	sort(e + 1, e + m + 1, cmp);
	for (int i = 1; i <= n; i++){
		sz[i] = 1;
		fa[i] = i;
	}
	work(0, e[m].w + 1, 1);
}

所以,这个东西......算了,不多评价吧......

相关推荐
Once_day6 小时前
C++之《程序员自我修养》读书总结(1)
c语言·开发语言·c++·程序员自我修养
Trouvaille ~6 小时前
【Linux】TCP Socket编程实战(一):API详解与单连接Echo Server
linux·运维·服务器·网络·c++·tcp/ip·socket
觉醒大王6 小时前
强女思维:着急,是贪欲外显的相。
java·论文阅读·笔记·深度学习·学习·自然语言处理·学习方法
偷吃的耗子7 小时前
【CNN算法理解】:CNN平移不变性详解:数学原理与实例
人工智能·算法·cnn
三水不滴7 小时前
计网:输入网址到网页显示
经验分享·笔记·计算机网络
坚果派·白晓明7 小时前
在鸿蒙设备上快速验证由lycium工具快速交叉编译的C/C++三方库
c语言·c++·harmonyos·鸿蒙·编程语言·openharmony·三方库
小镇敲码人7 小时前
深入剖析华为CANN框架下的Ops-CV仓库:从入门到实战指南
c++·python·华为·cann
dazzle7 小时前
机器学习算法原理与实践-入门(三):使用数学方法实现KNN
人工智能·算法·机器学习
那个村的李富贵7 小时前
智能炼金术:CANN加速的新材料AI设计系统
人工智能·算法·aigc·cann
张张努力变强8 小时前
C++ STL string 类:常用接口 + auto + 范围 for全攻略,字符串操作效率拉满
开发语言·数据结构·c++·算法·stl