• 「UER#2」谣言的传播


    「UER#2」谣言的传播

    写了个乱搞,怎么莫名其妙就AC了,这...,之后又想了30min结合题解终于会证了。

    首先最大值比较简单,记 (f_i) 为第 (i) 个点能到达的点数,上界 (sum_{i=1}^n f_i) 一定可以取到。考虑取到是这么一件事情,如果 (b_x=y) 那么 (y) 一定不能是 (x) 在外向树上的祖先以及环上的节点,外向树的根的前驱例外。那么有这么一个贪心,枚举每一棵外向树,用一个栈维护当前节点可以匹配的节点,一开始前驱在栈中。对于每一个节点,先优先匹配完其子树,然后再从栈中选取一个节点给它匹配。这样保证了如果存在完美匹配,一定不存在会令一个点取不到 (f_i) 的情况。然后证明,这样一定有完美匹配,考虑每一个节点,如果其不是叶子,那么其一定可以与其的某个孩子匹配。如果是叶子,那么只有遍历到第一个的叶子时候栈中不存在非其祖先节点,其余情况因为之前已经遍历了至少一个叶子,所以之前的到它的路径上至少遇到了一个孩子数量 (>1) 的节点,而一棵外向树做完以后至少会提供一个未匹配节点,这个叶子必然可以与那个未匹配节点匹配。最后再让第一个遍历到的叶子与前驱匹配即可。

    最小值比较难,考虑这么一件事情,一个节点如果与其子树中的节点匹配,则会使答案减少 (f_i-1) ,如果外向树的根 (x) 与环上某个节点 (y) 匹配,则会使答案减少 (lenth-dis(x,y)) 。还是类似的构造方法,只不过将原来 (x)(y) 匹配改成 (y)(x) 匹配。考虑一个叶子节点,其一定不能和子树内匹配。考虑一个非叶子节点,其一定可以和其一个儿子匹配。特殊的,如果一棵外向树只有一个点,它可以与环上的后继匹配,这样使 (len-dis(x,y)) 取到最大,这样我们保证了只要能取 (f_i-1) 就一定取到,不能取到的情况下也取到了最优的值,这是个紧的下界。

    用栈是因为在求最大值的过程中,即使不匹配儿子也是可以的,但是求最小值的过程中要严格与儿子匹配,所以用栈维护未匹配点可以保证这一点,复杂度 (mathcal O(n))

    code

    /*program by mangoyang*/
    #include <bits/stdc++.h>
    #define inf (0x7f7f7f7f)
    #define Max(a, b) ((a) > (b) ? (a) : (b))
    #define Min(a, b) ((a) < (b) ? (a) : (b))
    typedef long long ll;
    using namespace std;
    template <class T>
    inline void read(T &x){
    	int ch = 0, f = 0; x = 0;
    	for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1;
    	for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48;
    	if(f) x = -x;
    }
    const int N = 200005;
    ll ans, ans2;
    stack<int> st;
    vector<int> g[N];
    int vis[N], ins[N], inc[N], f[N], dep[N], c[N], b[N], n;
    inline int getcircle(int u, vector<int> &A){
    	if(ins[u]) return 1;
    	ins[u] = 1;
    	for(auto v : g[u]){
    		int tmp = getcircle(v, A);
    		if(tmp == 0) continue;
    		if(tmp == 2) return 2;
    		A.push_back(v);
    		return u == A[0] ? 2 : 1;
    	}
    	return ins[u] = 0, 0;
    	
    }
    inline void mark(int u){
    	vis[u] = 1;
    	for(auto v : g[u]) if(!vis[v]) mark(v);
    }
    inline void gao(int u, int fa, int lenth){
    	dep[u] = dep[fa] + 1;
    	f[u] = dep[u] + lenth - 1;
    	ans += f[u], ans2 += f[u];
    	int flag = 0;
    	for(auto v : g[u]) if(v != fa && !inc[v]){
    		flag = 1;
    		gao(v, u, lenth), st.push(v);
    	}
    	b[u] = st.top(), st.pop();
    	if(flag) ans2 -= f[u] - 1;
    	if(!flag && !fa) ans2 -= lenth - 2;
    }
    int main(){
    	read(n);
    	for(int i = 1, x; i <= n; i++)
    		read(x), g[x].push_back(i);
    	for(int i = 1; i <= n; i++) if(!vis[i]){
    		vector<int> A;
    		getcircle(i, A), mark(i);
    		reverse(A.begin(), A.end());
    		for(auto x : A) inc[x] = 1;
    		int size = A.size();
    		for(int i = 0; i < (int) A.size(); i++){
    			st.push(i == size - 1 ? A[0] : A[i+1]);
    			gao(A[i], 0, (int) A.size());
    		}	
    	}
    	for(int i = 1; i <= n; i++) c[b[i]] = i;
    	printf("%lld
    ", ans2);
    	for(int i = 1; i <= n; i++) printf("%d ", c[i]);
    	puts("");
    	printf("%lld
    ", ans);
    	for(int i = 1; i <= n; i++) printf("%d ", b[i]);
    }
    
  • 相关阅读:
    Sqlserver 2008:sp_msforeachdb 坑爹的错误陷阱
    安装官方 Synaptics 驱动,终于解决 HP Pavilion G4 笔记本 讨厌的触摸板锁定问题!
    Sqlserver 2008+:变更数据捕获(CDC) 和 更改跟踪(CT)
    PHP.ini 性能优化
    PHP Notice: Undefined index: ... 问题的解决方案
    查询类方法代码分析
    页面跳转方法总结大全
    如何关闭searchIndexer.exe进程
    如何正确运用PHP ini_set函数
    PHP中的串行化变量和序列化对象
  • 原文地址:https://www.cnblogs.com/mangoyang/p/11708860.html
Copyright © 2020-2023  润新知