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
*/
posted @ 2021-07-20 16:34  oisdoaiu  阅读(39)  评论(0编辑  收藏  举报