[CSP-S2019] 树上的数 树上推理
2021/11/7 23:15:36
本文主要是介绍[CSP-S2019] 树上的数 树上推理,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
还没整完明天再说,靠不小心点了发布、、
Link
某些废话
devinwang勒令我们补掉CSP2019的题 /youl
看了半天题解脑子里还是浆糊,退役人是我这样的。
这篇题解写的非常清楚,我写这个只是给我自己看。
题意
现在给你一棵树,数字 \(i\) 在编号为 \(p_i\) 的节点上。
每次删除一条边,删边时交换两节点上的数字。
定义数组 \(ans\),\(ans_i\) 是删除所有边时,数字 \(i\) 所在的节点编号。
求字典序最小的 \(ans\)。
\(n\le 2000\)
题解
很显然,我们从小到大考虑每个数字,尽可能的把它填在编号最小的节点上。
考虑部分分。
菊花图
假设我们初始是这样一张图。(图中节点上写的数字为原始节点编号,边上是边的编号)
如果依次删去 1,2,3,4,5 这些边,最终的图将会是:
如果我们将每个节点上初始和最终的标号相连,那么就会获得
这是个环。
(实际上你无论哪种情况最后形成的不都是若干个环……只不过菊花图只有一个……)
这样我们使用并查集维护联通性就可以获得菊花图的部分分。
代码中 \(i\) 为当前数字,\(x\) 是初始节点,\(j\) 是目标节点。显然只能在连最后一条边的时候出现环。
for(int i = 1; i <= n; i++) { int x = p[i]; for(int j = 1; j <= n; j++) { if(!vis[j] && (i == n || find(x) != find(j))) { printf("%d ", j); fa[find(x)] = find(j); vis[j] = 1; break; } } }
链
我们考虑让节点2上的数字,移动到节点4上去,那么我们就有限制
- 节点2 的两条边2、3中,3号边最先出现。
- 3号边的出现位置 < 4号边。不妨直接认定3号边和4号边的出现位置连续,因为考虑1号边在中间插入或不插入对答案可不可行根本没影响。
- 节点4 的两条边4、5中,4号边最晚出现。
那么就可以定出这些边之间的限制,如果违背了之前定下的限制,那肯定不可行。比如我不能先要求节点2的数字移动到节点4上去,然后又要求节点1上的数字移动到节点3上去。
具体可以用0,1,2表示没有限制,左<右,左>右表示一个点两侧边的限制,具体实现我没写。
正解
我们从小到大考虑这些数字,贪心的选择最小的可行的目标节点,然后更新信息。
链的部分分启发了我们考虑边和边之间的限制。
如果将节点 \(u\) 上的数字移动到节点 \(v\),那么一定满足
- 始边,是节点 \(u\) 所有边中最早出现的
- 终边,是节点 \(v\) 所有边中最晚出现的
- 中间的边,前一条比后一条边出现的早,考虑到不能被其他的边扰乱,无关的边是否在中间也对答案可不可行没有影响,所以不妨直接要求前后两个必须位置连续。
那么我们考虑怎么用并查集维护这个东西。
对于每个节点 \(u\),我们开一个虚点 \(u\)。对于每个虚点 \(u\) 开一个并查集数组 \(fa\)
- 如果节点 \(u\) 的边 \(edge\) 是所有边中最早出现的,就连有向边 \((u,edge)\)
- 如果 \(edge\) 是所有边中最晚出现的,那么就连有向边 \((edge,u)\)
- 如果边 \(i\) 和边 \(j\) 被要求位置连续,那么连有向边 \((i,j)\)
那么哪些情况是不可行的呢?
- 想要钦定某条边的 \(in/out\),这条边之前已经被钦定了 \(in/out\)。不能重复使用。
- 在节点 \(u\) 的所有边没有都用完之前,就出现了包含节点 \(u\) 的环。(菊花图当时的违法情况) 。这会导致有些边(原图中的边)优先级的没法钦定了。
钦定in和out挺好搞的,然后我们发现第二个很难维护。
其实只要稍微改一下就行了。
- 如果节点 \(u\) 的边 \(edge\) 是所有边中最早出现的,就合并 \((u,edge)\)
- 如果 \(edge\) 是所有边中最晚出现的,那么就连有向边 \((edge的反边,u)\)
- 如果边 \(i\) 和边 \(j\) 被要求位置连续,那么连有向边 \((i的反边,j)\)
所以我们实际上合并了虚点和他周围的边,而每条边和他的反边并没有合并。
所以虚点的sz维护的其实就是虚点连出的边使用了多少条+虚点自己。
并查集出现环的时候,实际上就是第一种建图方法里 虚点出发,到最后虚点结束的一个环。
虚点为 \(u\) 时,当并查集的 \(sz\) 为 \(deg_u+1\) 时,是合法的最后一次合并的环。
最后发现:本来应当对每个点开一个并查集,但实际上每个点所对应的虚点+每个点连出的边,显然是不相同的,所以直接开N*3大小的并查集即可。
代码
code
#include<bits/stdc++.h> using namespace std; typedef long long ll; #define mkp make_pair #define pb push_back #define PII pair<int, int> #define PLL pair<ll, ll> #define ls(x) ((x) << 1) #define rs(x) ((x) << 1 | 1) #define fi first #define se second const int N = 2010, M = N * 3; int n, mn, p[N], deg[N], ans[N]; int in[M], ot[M]; int e, hd[N], to[M], nxt[M]; int fa[M], sz[M]; void clear() { for(int i = 1; i <= e; i++) fa[i] = i, sz[i] = 1, in[i] = ot[i] = 0; } int find(int x) { return (x == fa[x]) ? x : fa[x] = find(fa[x]); } void merge(int x, int y) { int fx = find(x), fy = find(y); // if(fx == fy) return;已经规避了相等的情况。 fa[fx] = fy; sz[fy] += sz[fx]; ot[x] = in[y] = 1; return; } void add(int u, int v) { to[++e] = v; nxt[e] = hd[u]; hd[u] = e; } void init() { for(int i = 1; i <= n; i++) hd[i] = deg[i] = 0; for(int i = 1; i <= e; i++) to[i] = nxt[i] = 0; e = (n + 1) / 2 * 2 + 1; return; } bool check(int x, int y, int len) { if(in[y] || ot[x]) return 0; x = find(x); y = find(y); if(x == y && sz[x] != len) return 0; return 1; } void dfs(int u, int ed) { if(ed != u && check(ed, u, deg[u] + 1)) mn = min(mn, u); for(int i = hd[u]; i; i = nxt[i]) { int v = to[i]; if(i == ed) continue; if(check(ed, i, deg[u] + 1)) dfs(v, i ^ 1); } return; } bool dfs2(int u, int ed, int goal) { if(u == goal) return merge(ed, u), 1; for(int i = hd[u]; i; i = nxt[i]) { int v = to[i]; if(i == ed) continue; if(dfs2(v, i ^ 1, goal)) return merge(ed, i), 1; } return 0; } void solve() { for(int i = 1; i <= n; i++) { mn = n + 1; dfs(p[i], p[i]); dfs2(p[i], p[i], mn); ans[i] = mn; } for(int i = 1; i <= n; i++) printf("%d ", ans[i]); puts(""); return; } int main(){ int T; scanf("%d", &T); while(T--) { scanf("%d", &n); init(); for(int i = 1; i <= n; i++) scanf("%d", &p[i]); for(int i = 1, u, v; i < n; i++) { scanf("%d%d", &u, &v); add(u, v); add(v, u); deg[u]++; deg[v]++; } clear(); solve(); } return 0; }
这篇关于[CSP-S2019] 树上的数 树上推理的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23增量更新怎么做?-icode9专业技术文章分享
- 2024-11-23压缩包加密方案有哪些?-icode9专业技术文章分享
- 2024-11-23用shell怎么写一个开机时自动同步远程仓库的代码?-icode9专业技术文章分享
- 2024-11-23webman可以同步自己的仓库吗?-icode9专业技术文章分享
- 2024-11-23在 Webman 中怎么判断是否有某命令进程正在运行?-icode9专业技术文章分享
- 2024-11-23如何重置new Swiper?-icode9专业技术文章分享
- 2024-11-23oss直传有什么好处?-icode9专业技术文章分享
- 2024-11-23如何将oss直传封装成一个组件在其他页面调用时都可以使用?-icode9专业技术文章分享
- 2024-11-23怎么使用laravel 11在代码里获取路由列表?-icode9专业技术文章分享
- 2024-11-22怎么实现ansible playbook 备份代码中命名包含时间戳功能?-icode9专业技术文章分享