BZOJ3244:[Noi2013]树的计数 (树的遍历)

来源:互联网 发布:淘宝哪里可以回收手机 编辑:程序博客网 时间:2024/06/06 00:18

题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3244


题目分析:一道超级难想的题,我YY了好几天都不会做,最后只好%一波网上大神的题解QAQ。

由于编号没有什么用,我们将BFS序强行设为1~n,并对应地改DFS序。现在我们考虑对着BFS序分层,每一层对应BFS序上的一个区间。然后要分析出一下三个结论:
①:1号点单独分一层。
②:如果i号点在DFS序中的位置比i+1号点要后,那么i号点与i+1号点之间一定隔了一层。
③:考虑DFS序上相邻的两个点(a,b),很明显b的深度不会超过a的深度+1。所以如果a<b,应该限制a与b之间的分层数不超过1。

那么如何分层才能满足上述条件呢?我们先将①,②条件处理完,构造一个前缀和数组。然后逐一处理③条件,如果a与b之间已经有某个地方分了层,其它地方就一定不能分层。这可以通过O(1)地打标记,再离线扫一遍处理好。

那么最终的情况就是:某些相邻点对(i,i+1)之间一定要分层,某些一定不能,还有一些不确定。对于不确定的点对,它们之间分不分层,对其它点没有影响,所以它们对答案的贡献是0.5。

那么其实还有一个疑问:③条件其实是限制了某个区间的分层数小于等于1,可如果最后这个区间的所有相邻点对是否分层都是不确定的,那么每个点对对答案的贡献就不可以是0.5。但事实上这种情况是不会发生的。因为对于DFS序上相邻的点对(a,b),且a<b,如果a+1<b,那么它们之间肯定有个地方已经强制分了层,这就导致这个区间的其它地方一定不能分层。如果a+1=b,那么该点对产生0.5的贡献是不会有问题的。

注意在B站上提交,要输出3行答案,分别是ans-0.001,ans,ans+0.001。


下面扯一下我一开始拿到这题的一个O(n2)的想法:

首先还是重编号,然后考虑分治。令Solve(L,R)返回一个double数组a,其中a[i]表示DFS序上L~R的一段组成若干棵树,其中最高的深度为i的概率。则很明显令ans=Solve(2,n),1+ni=1ans[i]i就是答案。

再考虑一下怎么求Solve(L,R)。令x=DFS序的第L位,先取出L~R中从x开始的极长连续上升子串,设其长度为len。那么这一段组成高度为i的树,概率为Cilen2len,这个可以预处理一个杨辉三角。然后再取出从x开始的极长连续上升子序列,那么x+len-1以后的部分都和x+len-1高度相同,且它们之间划分出了一些子区间。递归求出这些子区间高度为某个数的概率,然后用容斥算一下这些子区间最高高度为i时候的概率,存进b数组。最后将b数组和杨辉三角的数组卷一下就可以了。由于预处理和分治的时间是O(n2),卷积的总时间不会超过点对的数量,也是O(n2)的,所以能过85%的数据。

然而事实上这样是有问题的,因为划分的子区间之间会相互影响。举个例子:
DFS[]={1,2,5,3,6,7,4,8,9,10}
划分成子区间{6,7}和{8,9,10}的时候如果前者的深度为2,后者的深度为1,就会和BFS序冲突。
尽管如此,这个方法还是拿了30分。或许是数据太弱了吧……


CODE:

#include<iostream>#include<string>#include<cstring>#include<cmath>#include<cstdio>#include<cstdlib>#include<stdio.h>#include<algorithm>using namespace std;const int maxn=200100;int bfsx[maxn];int dfsx[maxn];int val[maxn];int id[maxn];int sum[maxn];int cnt[maxn];int n;int main(){    freopen("3244.in","r",stdin);    freopen("std.out","w",stdout);    scanf("%d",&n);    for (int i=1; i<=n; i++) scanf("%d",&dfsx[i]);    for (int i=1; i<=n; i++) scanf("%d",&bfsx[i]);    for (int i=1; i<=n; i++) val[ bfsx[i] ]=i;    for (int i=1; i<=n; i++) dfsx[i]=val[ dfsx[i] ],id[ dfsx[i] ]=i;    for (int i=1; i<n; i++) if (id[i]>id[i+1]) sum[i]=1;    sum[1]=1;    for (int i=2; i<n; i++) sum[i]+=sum[i-1];    for (int i=1; i<n; i++)    {        int a=dfsx[i],b=dfsx[i+1];        if (a<b)        {            b--;            if (sum[b]-sum[a-1]>=1) cnt[a]++,cnt[b+1]--;        }    }    for (int i=n-1; i>=2; i--) sum[i]-=sum[i-1];    int now=0;    for (int i=1; i<n; i++)    {        now+=cnt[i];        if ( !now && !sum[i] ) sum[i]=2;    }    int temp=2;    for (int i=1; i<=n; i++)        if (sum[i]==1) temp+=2;        else if (sum[i]==2) temp++;    double ans=(double)temp/2.0;    printf("%.3lf\n%.3lf\n%.3lf\n",ans-0.001,ans,ans+0.001);    return 0;}
原创粉丝点击