2017-7-26 大佬讲课笔记

AntiLeaf大佬来讲课啦

完全 不可做 题的一天

NOI2016 区间

大佬填坑= =
传送门
把所有区间按长度排序,从小到大扫每个区间
显然最长的那个区间一定是随最小的区间单调增的,因此暴力拓展每个区间并用线段树求一下是否有被m个区间覆盖的点即可
O(nlogn)

正经的主题——概率与期望

基本工具

DP&&Gauss
DP的状态定义常常体现“逆序”的特点
状态之间不存在先后关系时往往需要解方程组
特殊方程组的奇技淫巧= =

bzoj1426

传送门
n种易拉罐,买第i次需要i元,假设每次买到的易拉罐种类都是随机的,问期望花多少钱能集齐所有易拉罐
n<=5×10^6
(不要问我为啥粘了传送门还粘题,两位大佬的题目描述可能是奇奇怪怪的不一样呢)

本蒟蒻

不就是n×∑(1/i)×average嘛(听取WA声一片- -)

AntiLeaf

设购买次数为x,则Ans=(E(x)+E(x²))/2
因为x次,需要x(x+1)/2元
E(x)=n
∑(1/i)
f[i]表示差i种易拉罐的期望次数,g[i]表示平方的期望
f[i]=f[i+1]+n/i
设之前的次数为x,获得第i种易拉罐还需要y次
E((x+y)²)=E(x²+2xy+y²)=E(x²)+2E(x)E(y)+E(y²)
我们已经知道E(x²)=g[i+1],E(x)=f[i+1],E(y)=n/i
剩下的问题就是如何求E(y²)了
从定义入手
E(y²)=∑(k 1~∞)k²((n-i)/n)^(k-1)×(n/i)
后面的东西硬推出通项(dalao的力量么= =)

liu_runda

我们可以在得到n种邮票后再计算费用,认为最后一张邮票的花费是1,倒数第二张邮票的花费是2,倒数第n张的花费是n,不难看出这样计算的费用是不变的.
如果不这样转换,也可以认为每一次购买邮票的花费都是1,而每次购买邮票会使后面的每次购买的花费增加1,也就是总费用增加了剩余的购买次数元
F[i]为买最后i张邮票的期望次数,C[i]为买最后i种邮票的期望花费(不包括得到前n-i种邮票使后i种邮票增加的花费)
C[i]=(i/n)×卫龙+(1-i/n)×香爆脆(达哥的变量名= =)
问卫龙和香爆脆是什么
其实就是分类讨论
卫龙=1(买1次)+F[i+1](增加的费用)+C[i+1]
香爆脆=1+F[i]+C[i]
最终C[i]=n/i+f[i+1]+C[i+1]+(n-i)*f[i]/i
这个费用提前计算的技巧还是有些用处的.
例如网络流里 SCOI2007修车和NOI2012美食节
再次说明概率和期望的题虽然有特性,但是和其他题的思路是相通的.

大佬出的水题

有一个人在一个序列上闲逛,初始时这个人位于位置1,每次他有pi的概率停下来(pi只与当前位置有关),也有1-pi的概率继续走下去(这人闲得慌- -)
如果他走到了序列的某一端,则他下次行走时会掉头往回走
求这个人走的次数的期望在模10^9+7意义下的结果(意思是必须求出精确解)
n<=210^5
直接处理这个人来回走的情况可能不太优美,我们可以直接把问题转化成这个人在一个环上沿固定方向走,把原序列除两端以外的部分复制一份并反向与原序列拼起来即可
然后考虑环上的问题,定义f[i]表示从i开始走的期望步数,显然有f[i]=(1-p[i])(f[i+1]+1)(如果这个人按照1~n的方向走的话)
然而这个转移出环了……
注意到转移时的运算都是线性的,因此我们可以从n开始倒推f[i]=a[i]
f[1]+b[i],最后推到f[1]时即可得到一个关于f[1]的一元一次方程,直接解出来即可
显然这个做法是O(n)的

水题增强版

把刚才的环换成一棵有根树,每次如果不停下来则会随机选择一个儿子走过去,规定走到叶子节点后再走会回到根节点,其余均不变
做法同上
把有根树换成有根森林,回到根节点改成随机回到一个根节点,其余均不变
建一个超(chao)级(shi)原点即可,其余做法同上
把有根森林换成DAG,随机回到一个根节点改成随机回到一个入度为0的点,其余均不变
建一个新点连向所有入度为0的点,其余做法还是同上……

CF464D World of Darkraft-2

传送门
翻译版= =:
你在玩一个游戏,游戏中有k种装备,每件装备都有一个等级,初始时你拥有每种1级装备各一件
你打算刷n只怪,每刷一只怪之后系统就会随机爆出一件装备
随机方式是先等概率随机装备的种类,设你当前拥有的这种装备的等级为t,则系统会在1~t+1之间等概率随机装备的等级
由于某些原因,你决定只在爆出的装备高于当前装备的等级时换上新装备并卖掉旧装备,否则直接卖掉爆出的装备,卖掉等级为t的装备可以得到t个金币
求你刷n只怪后得到的金币数的期望
n<=10^5,k<=100
可以发现每种装备对答案的贡献都完全相同且互不影响,因此我们可以只求出一种装备对答案的贡献,最后乘上k即可
一种装备的贡献可以用DP求解,设f[i,j]表示带着等级为j的装备再刷i只怪的期望收益(注意这里还是逆序定义),则有
f[i,j]=((f[i-1,j]+(j+1)/2)×(j/(j+1))+(f[i-1,j+1]+j)/(j+1))/k+(f[i-1,j]×(k-1))/k
(乱成啥了- -)
按照这个方程DP可以做到O(n²)
继续考虑如何优化
DP的转移已经无法再优化了,因此我们可以尝试优化状态数
注意到装备等级越高,再提升一级就越难,而刷n只怪之后装备的期望等级最多也是O(n^0.5)的,并且不难看出等级比O(n^0.5)高太多之后的概率就可以忽略不计了
因此我们可以把第二维的范围限定为<=一个O(n^0.5)级别的数,这样就可以把复杂度降到O(n^1.5)了

这道题告诉我们,做不要求输出精确解的概率/期望DP时可以忽略概率非常小的状态,通常可以起到卡常的作用,有时也能优化复杂度(比如这道题)

CF696B Puzzles

传送门
有一棵n个点的有根树,从根开始对这棵树进行dfs,对于一个点访问它的所有儿子的顺序是随机的
求每个点的时间戳的期望
n<=10^5
我们都知道期望有线性性
因此一个点的时间戳的期望=父亲的期望+兄弟对它的贡献+1
对每个兄弟分别考虑,如果这个兄弟比它更早访问,对它的时间戳的贡献就是子树大小
由于是随机访问,因此谁在前面的概率都是一样的
一个点的期望=父亲的期望+兄弟子树大小之和/2+1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
inline int read(){
int sum(0);
char ch(getchar());
for(;ch<'0'||ch>'9';ch=getchar());
for(;ch>='0'&&ch<='9';sum=sum*10+(ch^48),ch=getchar());
return sum;
}
struct edge{
int s,e,n;
}a[100001];
int pre[100001],tot;
inline void insert(int s,int e){
a[++tot].s=s;
a[tot].e=e;
a[tot].n=pre[s];
pre[s]=tot;
}
int size[100001],fa[100001];
double f[100001];
int n;
inline void dfs(int u){
size[u]=1;
for(int i=pre[u];i!=-1;i=a[i].n){
int e(a[i].e);
dfs(e);
size[u]+=size[e];
}
}
inline void cal(int u){
if(u!=1)
f[u]=f[fa[u]]+(size[fa[u]]-size[u]-1)/2.0+1;
// cout<<u<<' '<<f[u]<<endl;
for(int i=pre[u];i!=-1;i=a[i].n)
cal(a[i].e);
}
int main(){
memset(pre,-1,sizeof(pre));
n=read();
for(int i=2;i<=n;i++){
int x(read());
fa[i]=x;
insert(x,i);
}
dfs(1);
f[1]=1;
cal(1);
for(int i=1;i<=n;i++)
printf("%.1lf ",f[i]);
}
//首道cf上的题留念- -

GT考试

出门右转
大佬告诉我我那个鬼东西叫KMP自动机- -
然而我瞎打的时候并不知道自动机= =

总结

并不咋能听懂= =
还需消化吸收= =
ps:等我能做出来的时候在粘代码吧- -