首页 > 其他分享 >2024.1.21 ~ 2024.2.2 集训总结

2024.1.21 ~ 2024.2.2 集训总结

时间:2024-02-04 23:56:20浏览次数:29  
标签:2024.1 2024.2 21 int sum 路径 生成 回路 欧拉

集训大纲

  • Week1:
    • 图论:拓扑排序、欧拉回路、二分图、最小生成树
    • 数据结构:并查集、堆、单调队列
  • week2:
    • 图论:连通性
    • 数据结构:线段树

图论

拓扑排序

DAG 上的点以关联性进行排序,得到一个有关联的枚举顺序。

有了这种特别的枚举顺序,使得在 DAG 上 DP 的转移过程更加合理且有序。

所以,DAG \(\to\) DP \(\to\) 拓扑排序。

另类排序顺序

Eg. 菜肴制作
要求在满足拓扑序的条件下,小编号的尽可能靠前。

小编号靠前 = 大编号靠后

不妨倒序考虑,先将大编号的向前放,最后倒序输出。

那么由于要让原本在后的在前,相当于反转拓扑序,所以建反图,然后满足大编号向前,即字典序越大越好,所以将队列改成大根堆即可。

数量关系建图

Eg. 数列恢复

有一个长度为 \(n\) 的整数数列 。

定义 \(s_{i, j} = \sum_{k = i}^j a_k\) 。

你现在只知道所有 \(s_{i, j}\) 的符号,请恢复一个合法的数列,或指出不存在这样的数列。

\(s_{i, j}\) 表示的是区间和,那么转化一下,可以用前缀和表示区间和 \(sum_j - sum_{i - 1} = s_{i, j}\)。

那么已知 \(s_{i, j}\) 符号,\(sum_j\) 和 \(sum_{i - 1}\) 的大小关系也就知道了。

通过大小关系建图,\(e_{u, v}\) 表示 \(sum_u < sum_v\),然后拓扑排序构造 \(sum_v = \max(sum_u + 1)\) 即可。

那么 \(s_{i, j} = 0\) 呢?

既然 \(s_{i, j} = 0\) 表示 \(sum_{i - 1} = sum_j\) 那么不妨改变一下节点的定义:\(u\) 表示所有值为 \(sum_u\) 的点。当 \(s_{i, j} = 0\) 的时候 \(sum_{i - 1}\) 和 \(sum_j\) 等价,直接将其合并(用并查集维护起来),最后取值时直接取集合代表的值即可。

最后每个 \(a_i = sum_i - sum_{i - 1}\)。

丑陋代码
#include <bits/stdc++.h>
using namespace std;
const int N = 100 + 10;
int n;
int f[N];
char s[N][N];
vector<int> G[N], g[N];
int find (int x) {
	if (x == f[x]) return x;
	return f[x] = find(f[x]);
}
int in[N], a[N];
map<int, int> mp;

signed main() {
	ios::sync_with_stdio(0);
	while (cin >> n) {
		mp.clear();
		for (int i = 0; i <= n; i ++) f[i] = i, G[i].clear(), g[i].clear(), a[i] = 0, in[i] = 0;
		for (int i = 1; i <= n; i ++) {
			for (int j = i; j <= n; j ++) {
				cin >> s[i][j];
				if (s[i][j] == '+') {
					G[i - 1].push_back(j);
				} else if (s[i][j] == '-') {
					G[j].push_back(i - 1);
				} else {
					f[find(j)] = find(i - 1);
				}
			}
		}
		for (int i = 0; i <= n; i ++) {
			for (auto u : G[i]) {
				g[find(i)].push_back(find(u)), in[find(u)] ++;
			}
		}
		queue<int> q;
		int tot = 0, idx = 0;
		for (int i = 0; i <= n; i ++) {
			if (!mp[find(i)]) {
				if (!in[find(i)]) q.push(find(i));
				idx ++;
				mp[find(i)] = 1;
			}
		}
		while (!q.empty()) {
			int u = q.front();
			q.pop(), tot ++;
			for (auto v : g[u]) {
				a[v] = max(a[v], a[u] + 1);
				if(!(-- in[v])) {
					q.push(v);
				}
			}
		}
		if(tot != idx) cout << "NO\n";
		else {
			cout << "YES\n";
			for (int i = 1; i <= n; i ++) {
				cout << a[find(i)] - a[find(i - 1 )] << ' ';
			}
			cout << endl;
		}
	
	}
	return 0;
}

说实话,如果不知道算法是拓扑排序,这道题我绝对不知道要往图论方向想。

欧拉回路

这个知识点还是很陌生的,毕竟 GG 只给我们做了板题

定义

一条经过了图 \(G\) 所有的边仅一次的路径,即为欧拉路径(一笔画)。
特别的,当起点与终点相同时,称为欧拉回路。

判别

大前提:该图弱联通(有向)或联通(无向)

定义:\(in_u,out_u\) 分别表示有向图中一个点的入度和出度,\(d_u\) 表示一个无向图中的一个点的度数。

  1. 有向图
    当且仅当所有点 \(in_u = out_u\) 时,该有向图存在欧拉回路。
    当且仅当除起点和终点以外的点 \(in_u = out_u\),且 \(in_{起点} = out_{起点} - 1,in_{终点} = out_{终点} + 1\),该有向图存在欧拉路径。
  2. 无向图
    当且仅当该图仅有 \(0\) 个奇点,该图存在欧拉回路。
    当且仅当该图仅有 \(2\) 个奇点,该图存在欧拉路径。

寻找

从奇点(欧拉路径)或任意一点(欧拉回路)开始 DFS,每次经过一条边就将其删去(保证每一条边仅经过一次),然后以后序输出。

对于欧拉回路,显然这样输出实际上即为遍历顺序,并且每次回溯前一定已经将该点可达的所有环全部走过;但对于欧拉路径,遍历过程中可能会在走一部分环后走到终点并且无法继续,此时会出现回溯并重新遍历路径上尚未遍历的环,也就是方案不能仅仅按照遍历顺序。因此这时按照后序输出,就相当于首先保证遍历所有环然后再考虑剩余的路径,从而保证了正确性。

实现技巧:

  • 删边:可以使用链前,由于每次存边时,正边和反边的编号 \(\oplus 1\) 即可得到另一边的编号,所以每次删除 \(i\) 和 \(i \oplus 1\) 号边即可。

考察方向:

建模

  1. 宝石装箱

    有 \(4n\) 颗宝石,价值分别为 \(1 \sim 4n\) 。
    宝石的颜色共有 \(n\) 种,每种颜色恰好有 \(4\) 颗宝石。
    现在要将宝石装进两个箱子,满足以下两个条件:

    • 两个箱子的宝石总价值相等;
    • 每个箱子每种颜色恰有 \(2\) 颗宝石。

    请你设计一种划分方案,数据保证一定存在符合条件的划分方案。

    条件 1 很好满足,每次选取 \((i, 4n - i)\) 的宝石放入一个箱子中,\((i + 1, 4n - i - 1)\) 放入另一个箱子中即可。

    现在关键在于要满足条件 2。

    对于每组 \((c_i, c_{4n - i})\) 将其视作边,该图一定存在欧拉回路:每种颜色必定度数为 \(4\)。又因为刚才满足条件 \(1\) 时的奇偶分组,所以求出该图的欧拉回路之后,奇偶间隔取边所对应的 \((i, 4n - i)\) 即可。

构造

  1. 最少路径覆盖问题

    要求:简单路径,每条路径不能重复

    由于欧拉路径是保证不重复经过每一条边的,所以可以尝试用欧拉路径去覆盖这张图。

    若该图没有奇点,\(1\) 条欧拉路径即可覆盖完整个图。

    若该图奇点数 \(\ge 2\),那么该图会长这样:

    image

    人类智慧

    由于握手定理,奇点数量为偶数个。

    若要使其被多条欧拉路径所覆盖,可以先将两两奇点连边,使得该图为欧拉图,然后找出该图的欧拉路径,删除其中的虚边,即为覆盖完该图需要的欧拉路径条数。

    所以最后答案即为 : \(cnt / 2\)。

    NKOJ P8436 守卫王国
    #include <vector>
    #include <iostream>
    #include <stack>
    #include <cstring>
    using namespace std;
    #define endl '\n'
    const int N = 5e5 + 10;
    int n, m;
    struct edge {
    	int to, nxt, id;
    }e[N];
    int h[N], idx, tmp;
    void add (int u, int v, int id) {
    	e[idx] = edge{v, h[u], id}, h[u] = idx, idx ++;
    	e[idx] = edge{u, h[v], - id}, h[v] = idx, idx ++;
    }
    vector<int> col[N];
    int bel[N], tot;
    int vis[N];
    void findltk (int u) {
    	col[tot].push_back(u), bel[u] = tot;
    	for (int i = h[u]; ~i ; i = e[i].nxt) {
    		if(!bel[e[i].to]) findltk(e[i].to);
    	}
    }
    int d[N];
    bool mark[N];
    stack<int> ans;
    void dfs (int u) {
    	for (int i = h[u]; ~i; i = e[i].nxt) {
    		if(mark[i]) continue;
    		h[u] = e[i].nxt;
    		mark[i] = mark[i ^ 1] = 1;
    		dfs(e[i].to);
    		ans.push(e[i].id);
    	}
    }
    signed main () {
    	ios::sync_with_stdio(0);
    	cin >> n >> m;
    	memset(h, -1, sizeof h);
    	while(m --) {
    		int u, v;
    		cin >> u >> v;
    		add(u, v, ++ tmp), d[u] ++, d[v] ++;
    	}
    	for (int i = 1; i <= n; i ++) {
    		if(! bel[i]) ++ tot, findltk(i);
    	}
    	int num = 0;
    	for (int i = 1; i <= tot; i ++) {
    		int cnt = 0;
    		if(col[i].size() == 1) continue;
    		for (auto u : col[i]) {
    			if(d[u] & 1) cnt ++;
    		}
    		num += max(cnt / 2, 1); // 一条链删除两个奇点 => ans >= sum(max(cnt / 2, 1))
    		// 证明: 构造证明
    	}
    	cout << num << endl;
    	for (int i = 1; i <= tot; i ++) {
    		if(col[i].size() == 1) continue;
    		int cnt = 0;
    		vector<int> odd;
    		for (auto u : col[i]) {
    			if(d[u] & 1) cnt ++, odd.push_back(u);
    		}
    		if(cnt == 0) {
    			dfs(col[i][0]);
    			cout << ans.size() << ' ';
    			while(!ans.empty()) cout << ans.top() << ' ', ans.pop();
    			cout << endl;
    		} else {
    			for (int j = 0; j < odd.size(); j += 2) {
    				if(cnt == 2) break;
    				add(odd[j], odd[j + 1], 0);
    				d[odd[j]] ++, d[odd[j + 1]] ++, cnt -= 2;
    			}
    			int st;
    			for (auto u : col[i]) if(d[u] & 1) st = u;
    			dfs(st);
    			while(1) {
    				vector<int> ve;
    				while(!ans.empty() && ans.top() != 0) ve.push_back(ans.top()), ans.pop();
    				cout << ve.size() << ' ';
    				for (auto v : ve) cout << v << ' ';
    				cout << endl;
    				if(ans.size()) ans.pop();
    				if(ans.empty()) break;
    			}
    		}
    	}
    	return 0;
    }
    

生成树

常用算法:

  • kruskal (加边、\(O(m \log_2 m)\))
  • prim (加点、\(O(n^2)\)、适合完全图)

考察方向

生成树上操作

一般和数据结构绑在一起,在求出最小生成树后,根据最小生成树的性质,求解问题。

  1. 最小生成树边分类

    判断每条边是一定出现在最小生成树上还是可能还是不可能

    首先先将任意一颗最小生成树找出来,然后讨论每条非树边。

    1. 如果该边连接的两点在生成树上的边中存在边长与其相等的边,那么该边可以替换这些与其边长相等的边,此时用倍增标记所有与其相等的边,它们也不是唯一的了。
    2. 否则,这条边一定不存在最小生成树上。
  2. 次小生成树
    首先,次小生成树有一个重要的性质:它与最小生成树的差别只有一条边。

    证明:
    假设将边 \(e1\) 和 \(e2\) 均改为 \(\ge\) 其的边 \(e3,e4\),可以得到次小生成树。
    那么,记除去 \(e1,e2\) 的边权和为 \(res\),即 \(res + e1 + e2 \le res + e3 + e4\)
    即 \(res \le res + e3 - e1 + e4 - e2\)
    又因为 \(e3 \ge e1,e4 \ge e2\) 所以后边的书均大于等于 \(0\),那么只加一个肯定小于等于加两个,所以只改一条边更优。
    那么,对于每一条非树边,该边连接的两点在生成树上的边中找出以该边替换后增量最小的,即路径最大值,然后取每条非树边替换后结果的最小值即可。同样的,路径最大值也可以使用倍增维护。

建模

  1. 跨越雷区
    一个点与两个雷区距离的最小值最大化,即这两个雷区距离 \(/ 2\),所以为了让与雷区距离最大值最小化,不妨连接所有雷区,然后找出最小生成树上最大边,即为路径上最大值距离 \(\times 2\),答案 \(\2\) 即可。(注意任意雷区可互达,所以是完全图,使用 Prim 算法)

连通性

最关键的:DFS 树

强连通分量

定义不再赘述(虽然上一次总结的时候也没写)。

在处理有环有向图的问题时,如果缩点后每个强连通分量里的信息可以方便处理出来的化,不妨先缩点,然后处理强连通分量内部信息,再在新的 DAG 上,进行拓扑排序、最短路……化繁为简。

双连通分量

该知识点掌握不牢,很多题不会做

性质

边双和点双缩点后均得到一棵树

  • 点双
    • 每个 \(size > 2\) 的点双中,每一条边都在一个简单环中,特别的,当点数 \(=\) 边数时,该点双中的所有边都仅在一个简单环中。
    • 点双中任意两个点都存在两条经过点不相同的路径。
    • 点双不具有传递性。
    • 非割点仅属于一个点双中。
    • 每条边仅属于一个点双中。
  • 边双
    • \(u, v\) 边双联通即两点间没有必经边。
    • 边双联通具有传递性,\(u,v\) 和 \(v, w\) 边双联通,那么 \(u, w\) 边双联通。
    • 对于边双内任意一条边 \((u, v)\),存在经过 \((u, v)\) 的回路。
    • 对于边双内任意一点 \(u\),存在经过 \(u\) 的回路。

解题

如果看到题目中出现:删去某一点,删去某一边,可以往点双或边双的方向想。

az……真写不出来了。

DS

DSU

维护许多树形集合的一种数据结构,复杂度接近常数。

支持操作:

  • 合并
  • 删除(需要建虚点)

优化:

  • 路径压缩(几乎必备)
  • 按秩合并

难点:

本体并不算很难,难在运用和建模,一般维护父子结构时可以使用。

单调队列

在 \(O(n)\) 时间内求出每一个长度为 \(k\) 的区间的极值。

同样,本体没什么花样,一般用于优化 DP。

这里重点说优化 DP。

对于形如 \(dp_i = \max_{j = i - k} ^ {i - 1} {dp_j + a_i + b_j}\) 的转移方程,首先尽可能将与变量 \(j\) 不相关的常数移动至循环外,使得循环内的只与 \(j\) 有关,那么此时,循环内的即可用单调队列维护出来。

线段树

本体可以出得很花,运用也可以很广。

本体

一些区间操作,区间查询的裸题,专门考验对线段树的熟练程度。

可能出询问的东西很奇怪,但是可以线段树区间合并来合并出答案的问题。

运用

好像我真的不会用

可以优化 DP,即区间 \(\max,\min,\sum\) 操作时可以使用线段树,一般不需要变动转移方程,直接 updata(dp[i]) 原值即可。

其它的真的不会了。

标签:2024.1,2024.2,21,int,sum,路径,生成,回路,欧拉
From: https://www.cnblogs.com/Ice-lift/p/18007242

相关文章

  • 2024.2.4寒假每日总结26
    算法题:292.Nim游戏-力扣(LeetCode)LeetCodeNim游戏292.Nim游戏-力扣(LeetCode)题目描述你和你的朋友,两个人一起玩Nim游戏:桌子上有一堆石头。你们轮流进行自己的回合,你作为先手。每一回合,轮到的人拿掉1-3块石头。拿掉最后一块石头的人就是获胜者。......
  • 学习记录21
    本次学习学习了Spark的Streaming的一些外来输入源进行操作的形式文件流创建一个文件[atguigu@hadoop102~]$cd/usr/local/spark/mycode/[atguigu@hadoop102mycode]$mkdirstreaming[atguigu@hadoop102mycode]$cdstreaming[atguigu@hadoop102streaming]$mkdir......
  • 【DM】-6521: 当前触发器不支持DDL语句
    问题:当代码块中有DDL(create,delete,alter)等操作时,报错“当前触发器不支持DDL语句”。这个问题是因为DDL_TV_TRIGGER参数值为0导致解决:需要在数据库目录下面的ini文件中增加DDL_TV_TRIGGER该参数解决;默认情况下,该参数值为0是关闭的;首先查询配置文件中参数名称包含DDL;(其实我在第一......
  • [SWPUCTF 2021 新生赛]Do_you_know_http
    HTTP请求头中各字段解释Accept :浏览器(或者其他基于HTTP的客户端程序)可以接收的内容类型(Content-types),例如Accept:text/plain。Accept-Charset:浏览器能识别的字符集,例如Accept-Charset:utf-8Accept-Encoding:浏览器可以处理的编码方式,注意这里的编码方式有别于字符集,这......
  • (2024.1.29-2024.2.4)C语言学习小结
    本周主要围绕《HeadfirstC》这本书展开C语言学习,按照计划,我学习了的内容。基本内容这周学习的内容像是上学期最后的内容的扩展、延申、深入,高级函数那块有点绕但慢慢啃下来还可以接受。以下是思维导图:遇到的问题与解决、经验教训等问题0(上周的问题这周才解决):看到书里......
  • 2024最新Office 2021 专业增强版密钥
    Office2021专业增强版是微软面向个人和小型企业的办公软件套装,包含Word、Excel、PowerPoint、Outlook、Access、Publisher、OneNote、Teams等应用程序。该版本新增了协作功能、云服务支持、人工智能增强等功能,可满足用户日常办公、商务协作、数据分析等需求。Office2021fo......
  • 2024最新Office 2021 Mac版密钥
    Office2021Mac版是微软推出的办公套件,专为Mac操作系统优化。它包括经典的Office应用程序,如Word、Excel和PowerPoint,具备更强大的性能和更丰富的功能,以提高用户在Mac环境下的办公效率和体验。Office2021forMac官网兑换绑定电子版密钥在此版本中,微软注重用户体验,采用了现......
  • 2024.2.4日报
    今天完成了信息化领域热词分析,以下是截图为证首先是用python爬取数据、清洗数据、保存到数据库  在这个过程中有一些词条查不到对应的百度解释于是直接在数据库中用delete删除了另外存储到数据库中可能会乱序,进行了代码的调整 然后是部署springboot和vue项目对他......
  • 云小课|Runc容器逃逸漏洞(CVE-2024-21626)安全风险通告
    阅识风云是华为云信息大咖,擅长将复杂信息多元化呈现,其出品的一张图(云图说)、深入浅出的博文(云小课)或短视频(云视厅)总有一款能让您快速上手华为云。更多精彩内容请单击此处。runc官方发布安全公告,披露runc1.1.11及更早版本中存在容器逃逸漏洞,攻击者会利用该漏洞导致容器逃逸......
  • [UOD2021]虚幻引擎中Groom毛发系统的流程和应用 | Epic Games 孙丹璐
    传送门:[UOD2021]虚幻引擎中Groom毛发系统的流程和应用|EpicGames孙丹璐_哔哩哔哩_bilibili   一.资产与导入1.1Groom毛发系统中常见名词Strand:生成的最终视觉上看到的毛发人类的毛发尺寸大约在0.0017-0.0018cm,建议控制在0.008cm发际线、鬓角、碎发......