1. 程式人生 > 其它 >UOJ NOI Round #5 d1t3

UOJ NOI Round #5 d1t3

tag:思博,樹形dp


先解決第一個問題,給定一個 ddm 序判斷合法。

先考慮 ddm 序最小的那個葉子 \(x\),假設深度為 \(dep\),那麼 \(x\) 到根鏈上這些點的 ddm 序從上到下一定是 \([1,dep]\)

於是不難發現,若 \(a_x\notin[1,dep]\),那麼一定不合法。

由於這條鏈包含了前 \(dep\) 小,那麼如果 \(a_x\) 不在鏈上,即便它可以經過一些操作換到鏈上,它也無法繼續往下走了。

否則直接把 \(a_x\) 依次換下來即可,這樣會最小化對樹形態的影響。

這樣操作完以後,實際上是可以把這個葉子扔掉的,而且新的樹的合法性和原樹相同。

可以這樣考慮,對於鏈外的點,因為交換操作只在鏈上進行,所以不管怎麼交換,鏈上始終是前 \(dep\)

小,不會影響到鏈外的點換到鏈上。

而對於鏈上的點,這樣依次操作之後,不會改變它們的相對順序,所以也不會影響到它們之間的交換。

這裡實際上是按照後序遍歷進行構造,後序遍歷指按照 ddm 序從小到大依次處理每個兒子,然後再處理自己


但是這樣還是無法得到一個靠譜的做法,考慮總結一個結論:

\(mx[x]\) 為子樹 ddm 序最大值,那麼有解當且僅當 \(\forall x,mx[x]\ge a[x]\)

感性證明,可以藉助上述過程,只要我們保證在上述遞迴過程中,每一次選擇的葉子都合法即可。換句話說,要保證每次最左邊的葉子 \(x\)\(a_x\)\(x\) 到根鏈上出現過。

對於第 \(k\)

步,假設選擇的葉子為 \(x\),那麼 \(x\) 到根鏈的點權構成的集合一定為 \([1,mx[x]]\) 除去之前用過的 \(a_i\)

可以這樣理解,由於是按照後序遍歷構造,那麼處理到當前點時,前 \(k\) 步到根鏈的權值集合的並,一定剛好等於 \(x\) 到根以及 \(x\) “左邊” 所有兄弟子樹的點權的並,也就是\([1,mx[k]]\)

所以實際上只需要滿足 \(mx[x]\ge a[x]\) 就可以保證 \(a[x]\) 在到根鏈上出現過了(因為 \(a_i\) 互不相同)。

然後就可以拿到 \(2,5\) 的部分分了。


考慮一個 dp,設 \(f[x]\)\(x\)

的權值至少需要 \(f[x]\) 才能使子樹全部合法。

那麼轉移很簡單,初始化為 \(f[x]=a[x]-sz[x]+1\),然後可以貪心,把兒子按照 \(f\) 值從小到大處理,對於第 \(i\) 個兒子,它對 \(x\) 的限制就是至少要 \(f[i]-\)\(i-1\)個兒子的\(sz\)和。

只有 \(f[1]=1\) 時合法,然後按照剛才的順序 dfs 就行了。


#include<bits/stdc++.h>
using namespace std;

template<typename T>
inline void Read(T &n){
	char ch; bool flag=false;
	while(!isdigit(ch=getchar()))if(ch=='-')flag=true;
	for(n=ch^48;isdigit(ch=getchar());n=(n<<1)+(n<<3)+(ch^48));
	if(flag)n=-n;
}

enum{
    MAXN = 100005
};

int n, a[MAXN], mn[MAXN], dfn[MAXN], sz[MAXN], cnt;
vector<int>to[MAXN];

inline bool cmp(const int &u, const int &v){return mn[u]<mn[v];}

void dfs(int x){
    for(int v:to[x]) dfs(v);
    sort(to[x].begin(),to[x].end(),cmp); sz[x] = 1;
    for(int v:to[x]) mn[x] = max(mn[x],mn[v]-sz[x]), sz[x] += sz[v];
    mn[x] = max(mn[x],a[x]-sz[x]+1);
}

void mk(int x){dfn[x]=++cnt;for(int v:to[x])mk(v);}

int main(){
    int Case; Read(Case);
    Read(n);
    for(int i=1; i<=n; i++) Read(a[i]);
    for(int i=2, p; i<=n; i++) Read(p), to[p].push_back(i);
    dfs(1);
    if(mn[1]!=1) return puts("-1"), 0;
    mk(1);
    for(int i=1; i<=n; i++) printf("%d ",dfn[i]);puts("");
    return 0;
}

/*
1
8
8 4 6 1 5 2 7 3
1 1 1 1 1 1 1
*/