【BZOJ3244】【NOI2013】树的计数(神仙题)

57 篇文章 0 订阅

题面

BZOJ
这题有点假, bzoj b z o j 上如果要交的话请输出 ans0.001,ans,ans+0.001 a n s − 0.001 , a n s , a n s + 0.001

题解

数的形态和编号没有关系,因此对于 bfs b f s 序重标号,同时修改一下 dfs d f s 序方便做题。
因为 bfs b f s 的层数等于树高,所以我们相当于要把 bfs b f s 划分为若干段,
那么,每一种 bfs b f s 划分显然要么不合法,要么对应一种树的形态。
那么,我们来考虑 bfs b f s 划分的几个限制。
首先有一个很明显的限制:
如果划分了一段 [L,R] [ L , R ] ,那么 dfn[L]<dfn[L+1]<...<dfn[R] d f n [ L ] < d f n [ L + 1 ] < . . . < d f n [ R ]
原因很简单,因为一个点的所有儿子对应的出现顺序在 bfs b f s dfs d f s 序中相同
显然所有儿子都是从左往右遍历,因此这一项显然成立。
pos p o s dfs d f s 序的反数组,
考虑 dfs d f s 序中相邻的两个元素 x,x+1 x , x + 1
显然要么是父子关系,要么是兄弟关系,要么 x+1 x + 1 x x 祖先的儿子。
所以可以列出不等式dep[x+1]dep[x]+1
当然了,还有一个约束,根节点后面必须断开。

我们构建一个数组 s s s[i]=1的话就表示 i i i+1必须断开

归类一下,约束条件有三项:
1.根节点后面必须断开,而根节点一定是 bfs b f s 序中的 1 1
2.同一段[l,r]中的所有点满足 dfs d f s 序递增,即如果 pos[i]>pos[i+1] p o s [ i ] > p o s [ i + 1 ] ,那么必须断开,也就是 s[i]=1 s [ i ] = 1
3.如果 dfn[i]<dfn[i+1] d f n [ i ] < d f n [ i + 1 ] ,那么, dfn[i+1]1j=dfn[i]s[i]1 ∑ j = d f n [ i ] d f n [ i + 1 ] − 1 s [ i ] ≤ 1 ,这个式子在计算的时候需要满足 dfn[i]<dfn[i+1] d f n [ i ] < d f n [ i + 1 ] ,而根据推导 dep[i+1]dep[i]1 d e p [ i + 1 ] − d e p [ i ] ≤ 1

如果知道了 s s ,那么1+s[i]就是树高,也就是 bfs b f s 序分开的段数。

考虑一下贡献是怎么产生的。
两个相邻的 bfs b f s 序之间要么不能断开,贡献为 0 0
要么必须断开,贡献为1
要么随意,贡献为 0.5 0.5
只需要利用所有约束条件确定每个位置的贡献就好啦。
我是看得LCF学长的blog

#include<iostream>
#include<cstdio>
using namespace std;
#define RG register
#define MAX 222222
inline int read()
{
    RG int x=0,t=1;RG char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return x*t;
}
double ans;
int n,a[MAX],d[MAX],s[MAX];
int main()
{
    n=read();ans=1;s[1]=1;s[2]=-1;
    for(int i=1;i<=n;++i)a[d[i]=read()]=i;
    for(int i=1;i<=n;++i)d[a[read()]]=i;
    for(int i=1;i<=n;++i)a[d[i]]=i;
    for(int i=2;i<=n;++i)if(a[i]<a[i-1])s[i-1]++,s[i]--,++ans;
    for(int i=2;i<=n;++i)if(d[i-1]<d[i]-1)s[d[i-1]]++,s[d[i]]--;
    for(int i=1,t=0;i<n;++i)t+=s[i],ans+=(!t)?0.5:0;
    printf("%.3lf\n",ans+1);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值