题目传送门: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
#include
#include
#include
#include
#include
#include
#include
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; iif (id[i]>id[i+1]) sum[i]=1;
sum[1]=1;
for (int i=2; i1];
for (int i=1; iint a=dfsx[i],b=dfsx[i+1];
if (aif (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; iif ( !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;
}