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