题目

题目大意

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

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

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

期望的最终结果。


思考历程

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

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

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

于是打暴力:

枚举所有的排列,然后用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. spring data jpa使用 (转:http://www.manongjc.com/article/25284.html#four_1_7)

    Jap相关的使用 时间:2018-12-18 本文章向大家介绍Jap相关的使用,主要包括Jap相关的使用使用实例.应用技巧.基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下. ...

  2. 批量调用百度地图API获取地址经纬度坐标

    1 申请密匙 注册百度地图API:http://lbsyun.baidu.com/index.php?title=webapi 点击左侧 “获取密匙” ,经过填写个人信息.邮箱注册等,成功之后在开放平 ...

  3. Java迷宫代码,深度优先遍历

    此次迷宫深度优先遍历寻找路径采用栈结构,每个节点都有固定的行走方向(右下左上),除非一个方向走不通,不然会一条道走到黑. 如果路径存在,打印出行走路径,否则打印出迷宫不存在有效路径. 方向常量定义: ...

  4. rancher2.0 自定义应用商店(catalog)

    1.进入自定义应用商店页面 ===================================================== ================================ ...

  5. Myeclipse从外部导入项目时,jsp和html页面中所有的onclick="return xx()"位置均出现cannot return from outside function() or method()错误

  6. Grunt入门

    Grunt 新手一日入门 2014.06.20 前端相关 TOC 1. 用途和使用场景 2. 开发一个任务自动处理器 3. 开始学习 Grunt 3.1. 安装 Grunt 3.2. 生成 packa ...

  7. linux大神

    http://blog.csdn.net/skykingf/article/category/780616

  8. Delphi代码创建形式规范 1.0

                Delphi代码创建形式规范 1.0 本规范的目的:给自己的代码一个统一而标准的外观,增强 可读性,可理解性,可维护性 本规范的原则:名称反映含义,形式反映结构 1.单元风格 ...

  9. K最近邻(KNN,k-Nearest Neighbor)准确理解

    K最近邻(KNN,k-Nearest Neighbor)准确理解 用了之后,发现我用的都是1NN,所以查阅了一下相关文献,才对KNN理解正确了,真是丢人了. 下图中,绿色圆要被决定赋予哪个类,是红色三 ...

  10. CSS display overflow 属性 cursor光标类型

    display属性   功能:规则网页元素如何显示的问题.   取值:none(隐藏).block(以块元素显示).inline(以行内元素显示)   block:可以实现将行内元素转成块元素.   ...