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);
}

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

相关推荐
mit6.8242 小时前
437贪心
算法
梓䈑2 小时前
【Linux系统】实现线程池项目(含日志类的设计)
linux·服务器·c++
秋刀鱼程序编程2 小时前
Java编程基础入门(四)---选择循环语句
java·开发语言·算法
优雅的潮叭2 小时前
c++ 学习笔记之 volatile与atomic
c++·笔记·学习
wen__xvn2 小时前
基础算法集训第04天:选择排序和冒泡排序
数据结构·算法·leetcode
充值修改昵称2 小时前
数据结构基础:二叉树高效数据结构的奥秘
数据结构·python·算法
Zsy_0510033 小时前
【C++】类和对象(二)
开发语言·c++
宵时待雨3 小时前
STM32笔记归纳2:GPIO
笔记·stm32·嵌入式硬件
啊阿狸不会拉杆3 小时前
《机器学习》第四章-无监督学习
人工智能·学习·算法·机器学习·计算机视觉