zoukankan      html  css  js  c++  java
  • [CSPS2019] 树上的数 树上推理

    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\) 时,当并查集的 \(sz\)\(deg_u+1\) 时,是菊花图。
      (就是每个点,只和自己的出边连了,出边也不会和自己的反边连,所以成环的唯一可能就是出边连了出边,那样的话好像必须得是菊花图、算了,我也不太懂)

    代码

    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;
    }
    
    qaqaq
  • 相关阅读:
    GridView合并行代码
    GridView合并行头代码
    Javascript 的几种写法与提示
    [转] 浅谈MSSQL锁机制
    Two ways of using memory: Stack and Heap
    You have to know how .Net code "managed"?
    Sql Server Profiler
    InterProcess Communication
    database Index
    敏捷开发
  • 原文地址:https://www.cnblogs.com/zdsrs060330/p/15521850.html
Copyright © 2011-2022 走看看