题目

题目大意

给你一棵树,这棵树上的所有叶子节点的权值是随机的排列

两个人博弈,从根开始,轮流往下走。

先手希望权值最大,后手希望权值最小。

期望的最终结果。


思考历程

这场比赛实在是太丧心病狂了!两个期望题,有没有人性啊!

还是那种特别变态的期望……

想了好久,没有想出来……

于是打暴力:

枚举所有的排列,然后用DP计算答案。

如果喜欢,DP可以加上alpha-beta剪枝……当然我没加,因为在这题里面没有什么意义。

分数还给的挺大方的,30分。


正解

首先推个式子:

E(x)=∑xP(x=ans)=∑P(x≤ans)E(x)\\=\sum xP(x=ans) \\=\sum P(x \leq ans)E(x)=∑xP(x=ans)=∑P(x≤ans)

后面这个是什么鬼?

我们将第二个式子拆开,对于xxx,它贡献了xxx个P(ans=x)P(ans=x)P(ans=x)。

看看第三个式子,对于x′≤xx'\leq xx′≤x,它就可以加上xxx的贡献。这样的x′x'x′有xxx个,所以这是成立的。

接下来就是一个很巧妙的转化:

我们设结果为xxx,将大于xxx的记作111,将小于等于xxx的记作000。

这样就大大地简化了题目,因为我们只需要关心它的结果是否为111,而不需要关心结果是否恰好为xxx。

然后就是树形DP:设fi,j,0/1f_{i,j,0/1}fi,j,0/1​表示点iii为根的子树中,叶子节点的权值为000的个数是jjj,点iii的值为000或111(从下面转移上来的值)的方案数。

方程就不用说了吧……自己推。

这就是一个树上背包问题。

至于最终的答案,枚举xxx,它的贡献就是f1,x,1Cmx\frac{f_{1,x,1}}{C_m^x}Cmx​f1,x,1​​。(mmm为叶子节点的个数)

由于题目良心地让我们输出ans∗m!ans*m!ans∗m!,所以说,输出的就是f1,x,1∗x!∗(m−x)!f_{1,x,1}*x!*(m-x)!f1,x,1​∗x!∗(m−x)!

然而这个算法看上去是O(n3)O(n^3)O(n3)的,我在很长时间内也这么认为。

YMQ:我吸一口臭氧,也能过!

但时间复杂度实际上是O(n2)O(n^2)O(n2)。

为什么?

从最简单的开始考虑:如果这棵树是一棵二叉树,对于一个非叶子节点,当大小分布比较均匀时:

对于根节点,合并子树的时间是(n2)2=n24\left(\frac{n}{2}\right)^2=\frac{n^2}{4}(2n​)2=4n2​。

对于下一层,时间是2(n4)2=n282\left(\frac{n}{4}\right)^2=\frac{n^2}{8}2(4n​)2=8n2​。

再下一层,时间是4(n8)2=n2164\left(\frac{n}{8}\right)^2=\frac{n^2}{16}4(8n​)2=16n2​。

后面的就不枚举了。

把它们全部加起来,时间就趋近于n22\frac{n^2}{2}2n2​。

当大小分布不均匀时,我们考虑最极端的情况,时间复杂度还是O(n2)O(n^2)O(n2)。

至于不均匀而又不极端的情况……感性理解,不信邪的可以打一个DP来求最坏的情况。

考虑一下多叉树,发现其实际上是类似的,时间复杂度是O(n2)O(n^2)O(n2)。

最终我还是不会证明……


代码

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 5000
#define mo 1000000007
int n;
struct EDGE{
int to;
EDGE *las;
} e[N*2+1];
int ne;
EDGE *last[N+1];
inline void link(int u,int v){
e[++ne]={v,last[u]};
last[u]=e+ne;
}
long long jc[N+1];
int tot[N+1];
long long f[N+1][N+1][2];
void dfs(int x,int fa,bool flag){
if (last[x]->las==NULL && last[x]->to==fa){//叶子节点的情况
tot[x]=1;
f[x][0][1]=f[x][1][0]=1;
return;
}
EDGE *ei=last[x];//另外处理第一个儿子,这样就不用考虑初始化
if (ei->to==fa)
ei=ei->las;
dfs(ei->to,x,!flag);
memcpy(f[x],f[ei->to],sizeof f[ei->to]);
tot[x]=tot[ei->to];
ei=ei->las;
if (flag==0){
for (;ei;ei=ei->las)
if (ei->to!=fa){
dfs(ei->to,x,1);
for (int j=tot[x]+tot[ei->to];j>=0;--j){
//k=0
f[x][j][1]=(f[x][j][0]*f[ei->to][0][1]%mo+f[x][j][1]*f[ei->to][0][0]%mo+f[x][j][1]*f[ei->to][0][1]%mo)%mo;
f[x][j][0]=f[x][j][0]*f[ei->to][0][0]%mo;
for (int k=1;k<=tot[ei->to] && k<=j;++k){
(f[x][j][1]+=(f[x][j-k][0]*f[ei->to][k][1]%mo+f[x][j-k][1]*f[ei->to][k][0]%mo+f[x][j-k][1]*f[ei->to][k][1]%mo))%=mo;
(f[x][j][0]+=f[x][j-k][0]*f[ei->to][k][0])%=mo;
}
}
tot[x]+=tot[ei->to];
}
}
else{
for (;ei;ei=ei->las)
if (ei->to!=fa){
dfs(ei->to,x,0);
for (int j=tot[x]+tot[ei->to];j>=0;--j){
//k=0
f[x][j][0]=(f[x][j][0]*f[ei->to][0][0]%mo+f[x][j][0]*f[ei->to][0][1]%mo+f[x][j][1]*f[ei->to][0][0]%mo)%mo;
f[x][j][1]=f[x][j][1]*f[ei->to][0][1]%mo;
for (int k=1;k<=tot[ei->to] && k<=j;++k){
(f[x][j][0]+=(f[x][j-k][0]*f[ei->to][k][0]%mo+f[x][j-k][0]*f[ei->to][k][1]%mo+f[x][j-k][1]*f[ei->to][k][0]%mo))%=mo;
(f[x][j][1]+=f[x][j-k][1]*f[ei->to][k][1])%=mo;
}
}
tot[x]+=tot[ei->to];
}
}
}
int main(){
freopen("game.in","r",stdin);
freopen("game.out","w",stdout);
scanf("%d",&n);
jc[0]=1;
for (int i=1;i<=n;++i)
jc[i]=jc[i-1]*i%mo;
for (int i=1;i<n;++i){
int u,v;
scanf("%d%d",&u,&v);
link(u,v),link(v,u);
}
dfs(1,0,0);
long long ans=0;
for (int i=0;i<=tot[1];++i)
ans=(ans+f[1][i][1]*jc[i]%mo*jc[tot[1]-i]%mo)%mo;
printf("%lld\n",ans);
return 0;
}

总结

这道题有一个·很奇妙的思想,就在于∑xP(x=ans)=∑P(x≤ans)\sum xP(x=ans)=\sum P(x \leq ans)∑xP(x=ans)=∑P(x≤ans)。



通过这个东西,可以大大地简化题目。

当条件为“等于”的时候,我们可以试着转化成“大于”“小于”。

然后就是树上背包的时间复杂度……

[JZOJ5233] 【GDOI模拟8.5】概率博弈的更多相关文章

  1. GDOI模拟赛Round 1

    GDOI模拟赛Round 1 数据结构 题目描述:给出一个长度为\(n\)的序列,支持两种操作: 1.对某段区间都加上一个数 2.给出\(p.k\),求下面表示式对\((10^9+7)\)取模 \[\ ...

  2. 【JZOJ5233】【GDOI模拟8.5】概率博弈 树形dp+期望

    题面 小A和小B在玩游戏.这个游戏是这样的: 有一棵n个点的以1为根的有根树,叶子有权值.假设有m个叶子,那么树上每个叶子的权值序列就是一个1->m 的排列. 一开始在1号点有一颗棋子.两人轮流 ...

  3. 【NOIP模拟】jzoj5233概率博弈(树规)

    Description 小A和小B在玩游戏.这个游戏是这样的: 有一棵

  4. [jzoj5233]概率博弈(树形DP)

    Description 小A和小B在玩游戏.这个游戏是这样的: 有一棵

  5. 2018.10.17 NOIP模拟 发电机(概率dp)

    传送门 考试空间开大了爆零不然只有30分爆栈? 话说这题真的坑1e7没法写dfsdfsdfs 其实很好推式子. 考虑每个点安一个发动机的概率,推一波式子做个等比数列求和什么的可以证明出来是严格的1si ...

  6. Codeforces Round #417 (Div. 2)A B C E 模拟 枚举 二分 阶梯博弈

    A. Sagheer and Crossroads time limit per test 1 second memory limit per test 256 megabytes input sta ...

  7. NOIP模拟 赌博游戏 - 概率dp

    题意: 最近西雅图的高中校园里流行这样一个游戏. 我们有一个骰子,这个骰子有M个面,分别写着1..M,并且是个公平的骰子,换句话说,一次投掷时每个面朝上的概率是相同的. 游戏的组织者使用这个骰子进行N ...

  8. GDOI模拟4.11~4.13总结

    总体情况 省选前的第一场模拟,就连续三天垫底滚粗了. 三天下来,只做了第一天的签到题,然后再做了一些水题的暴力,还不得分. 三天分数:100/400+40/400+90/400=230/1200,得了 ...

  9. 7.12 NOI模拟赛 探险队 期望 博弈 dp 最坏情况下最优策略 可并堆

    LINK:探险队 非常难的题目 考试的时候爆零了 完全没有想到到到底怎么做 (当时去刚一道数论题了. 首先考虑清楚一件事情 就是当前是知道整张地图的样子 但是不清楚到底哪条边断了. 所以我们要做的其实 ...

随机推荐

  1. slot 的简单用法

    注:默认在父组件调用子组件时<SlotChild></SlotChild>中文字不会显示.但是在子组件加入<slot></slot>后,<Slot ...

  2. tensorflow run()和 eval()

    eval()只能用于tf.Tensor类对象,也就是有输出的Operation.对于没有输出的Operation, 可以用.run()或者Session.run() 所以我们训练的时候,对于优化器只能 ...

  3. 使用QSlider

    1.当绘制的线性图等需要水平拖动的时候(不用qwt里面的函数),可以用QSlider,代码如下 ui.horizontalSlider->setMaximum(); //需要拖动的越缓慢,平滑它 ...

  4. spark session 深入理解

    spark 1.6 创建语句 在Spark1.6中我们使用的叫Hive on spark,主要是依赖hive生成spark程序,有两个核心组件SQLcontext和HiveContext. 这是Spa ...

  5. 函数的属性和方法, apply和call的区别及bind的使用

    ==>我的新博客中 http://www.suanliutudousi.com/2017/08/27/%E5%87%BD%E6%95%B0%E7%9A%84%E5%B1%9E%E6%80%A7% ...

  6. 2019-5-21-win10-uwp-xaml-兼容多个版本条件编译

    title author date CreateTime categories win10 uwp xaml 兼容多个版本条件编译 lindexi 2019-5-21 11:19:3 +0800 20 ...

  7. JS事件 光标聚焦事件(onfocus)当网页中的对象获得聚点时,执行onfocus调用的程序就会被执行

    光标聚焦事件(onfocus) 当网页中的对象获得聚点时,执行onfocus调用的程序就会被执行. 如下代码, 当将光标移到文本框内时,即焦点在文本框内,触发onfocus 事件,并调用函数messa ...

  8. 如何使用flow进行静态类型检查

    Flow 是 facebook 出品的 JavaScript 静态类型检查⼯具.Vue.js 的源码利⽤了 Flow 做了静态类型检查,所以了解 Flow 有助于我们阅读源码. 为什么⽤ Flow? ...

  9. Linux CentOS 6.7 挂载U盘

    1. 首先查看U盘是否成功安装fdisk -l 2. 在/mnt下创建U盘目录mkdir /mnt/usb 3. 挂载U盘mount -t vfat /dev/sdb1 /mnt/usb 4. 卸载U ...

  10. Erlang学习记录:输入和输出

    输入和输出 输入和输出功能都被定义在io模块 输出功能非常常用,由于erlang项目没有可断点调试的IDE(或者说根本不需要),所以所有的调试操作都是由io输出 来调试的 io:get_line/1. ...