【NOI2013】树的计数
题目
https://www.luogu.org/problem/P1232
题意简述
给定一棵树的$dfs$序和$bfs$序,求它深度的期望。
题解
首先,显然的,以$bfs$序为自然排列,对$dfs$序做一次置换。
其次,我们知道,要对$bfs$序做划分,连续的一组代表这些节点的深度都是一样的,组和组之间的深度是递增的。
我们知道,如果我们做好了划分,一定能确定树的形态,这里给出一种方法:
1.首先$1$号节点肯定单独一组,子树是$dfs$序上$i \in [1..n]$的$dfn[i]$。
2.再找$2,3..$号节点,既然它们在一组,每个点找下一个点出现的位置,从它的位置到下一个点出现的位置即子树的区间。
3.以此类推。
这样划分一定是可以构造出来这棵树的。
但是当这个构造方法不能奏效时,就无法构造这棵树。有两种情况:
1.对于同组之间的元素,$bfs$序上,同一组的数的位置没有递增(即子树范围是负的)
2.对于不同组之间的元素,任意$dfs$序上连续的两点,它们之间要么是父-子关系,要么先上,再向下一步,即$dep[dfn[i]]+1 \ge dep[dfn[i+1]]$。
设$x_i$表示$i$和$i+1$间是否有划分。我们求的就是$\sum_{i=1}^{n-1}{x_i}$的期望,有
1.$x_1=1$
2.设$dfn[p_x]=x$,若$p_x>p_{x+1}$,则$x_i=1$(限制$1$的应用)
3.若$dfn[k]<dfn[k+1]$,则$\sum_{i=dfn[k]}^{dfn[k+1]-1}{x_i} \le 1$
确定答案的话,因为这是一个差分约束的式子,我们可以用模拟松弛操作来更新它,最后我们一定可以确定一些$x_i=1$,一些$x_i=0$,对于无法确定的,因为独立,我们直接把它的值设为$0.5$即可。
小结
这道题我认为是一道很难的题,因为不好找到切入点,我在思考的时候是考虑$dfs$序上连续的两点,它们的深度即$bfs$序大小的关系,这样做一些细节是无法考虑到的,换句话说,只考虑局部之间的关系,没有考虑整体的限制。之前也设想过划分$bfs$序的做法,事实上,这种做法是及其优美的,深度这个看似需要最大值的东西被转化成了序列上的元素和,如果能先想到这个优美的转化,不考虑构造答案的情况下,逆向思维看待这个问题大概是能想出来的。
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define ri register int #define N 200050 using namespace std; int dfn[N],bfn[N],p[N],dc[N]; int n; inline int read() { int ret=0,f=0; char ch=getchar(); while (ch<'0' || ch>'9') f|=(ch=='-'),ch=getchar(); while (ch>='0' && ch<='9') ret*=10,ret+=(ch-'0'),ch=getchar(); return f?-ret:ret; } int main() { n=read(); for (ri i=1;i<=n;i++) dfn[i]=read(); for (ri i=1;i<=n;i++) bfn[read()]=i; for (ri i=1;i<=n;i++) { dfn[i]=bfn[dfn[i]]; p[dfn[i]]=i; } dc[1]++; dc[2]--; double ans=2; for (ri i=1;i<n;i++) if (p[i]>p[i+1]) { ans++; dc[i]++; dc[i+1]--; } for (ri i=1;i<n;i++) if (dfn[i]<dfn[i+1]-1) { dc[dfn[i]]++; dc[dfn[i+1]]--; } for (ri i=1,w=0;i<n;i++) { w+=dc[i]; if (!w) ans+=0.5; } printf("%.3lf",ans); }