概率与期望

继数论和组合之后的第3大数学巨坑

基本概念和符号表述

该部分可参考必修二(人教版)最后一章,本质上是使用集合描述概率

  • 随机事件:满足下列条件的现象

    • 可以在相同的条件下重复进行
    • 实验结果不止一个,且所有结果可以事先预知
    • 实验前不确定出现什么结果
  • 样本空间 Ω: 随机试验所有可能结果组成的集合

  • 样本点Ω中的元素,即一个结果就是一个样本点

  • 随机事件(集合下)Ω的子集

  • A事件发生A事件包含的样本点中有一个样本点出现(成为实验后的结果)

  • 事件类型:在某条件下根据发生情况分类,具体包括:

    • 基本事件:由一个样本点组成的单个元素的集合
    • 必然事件:在某条件下必然发生,叫做相对于该条件的必然事件
    • 不可能事件:在某条件下不可能发生,叫做相对于该条件的不可能事件
    • 随机事件:在某条件下可能发生,也可能不发生,叫做相对于该条件的随机事件
  • 频数和频率:在相同条件下重复n次实验,事件A发生的次数nA叫做事件A的频数 f=nAn叫做事件A出现的频率

  • 概率:事件A的频率的稳定值(看似随意,实则是大数定律

  • 事件的关系与运算:运用集合运算进行事件转化和概率计算

    关系 定义 描述
    包含 如果事件A发生,那么事件B一定发生 AB
    相等 BA,AB A=B
    和(并)事件 AB的和事件发生,当且仅当事件A发生或者事件B发生 AB(A+B)
    积(交)事件(同时发生 AB的积事件发生,当且仅当A发生且B发生 AB(AB)
    互斥事件 AB为不可能事件 AB=
    对立事件 不是发生A,就是发生B AB=,AB=Ω,此时P(AB)=P(A)+P(B)=1
  • 古典概型:实验结果可推知,无需任何统计试验,通常满足:

    • 样本空间有限
    • 每个结果出现的可能性相同
    • 每个现象发生的事件互不相容
  • 概率性质

    • P(AB)=P(A)+P(B)P(AB)
    • AB=P(AB)=P(A)+P(B)
    • A1An均互斥,那么

    P(i=1nAi)=i=1nP(Ai)

    • n个独立事件(自己是否发生不影响其他事件是否发生),那么

    P(i=1nAi)=i=1nP(Ai)

下列知识参考选修部分

  • 条件概率 P(B|A):表示在A已经发生的条件下,B发生的概率,本质上是把B的样本空间换成了A集合

    P(B|A)=n(AB)n(A)=P(AB)P(A)

    该种概率下有一些推论

    • A,B互为独立事件,那么P(B|A)=P(B)
    • P(AB)=P(B|A)P(A)
    • P(Ω|A)=1
    • BC=,那么P(BC|A)=P(B|A)+P(C|A)
    • P(ΩB|A)=1P(B|A)
  • 全概率公式
    A1An两两互斥,i=1nAi=Ω,且P(Ai)>0,那么对BΩ,有

    P(B)=i=1nP(Ai)P(B|Ai)

    简单理解:事件B有多种发生因素,那么事件B发生的概率等于每个因素导致B发生的概率之和

  • 贝叶斯公式
    A1An两两互斥,i=1nAi=Ω,且P(Ai)>0,那么对BΩP(B)>0,有

    P(Ai|B)=P(Ai)P(B|Ai)P(B)=P(Ai)P(B|Ai)k=1n[P(Ak)P(B|Ak)]P(AiB)

    简单理解:事件B有多种发生因素,现在事件B已经发生,那么该式子可求得B发生的原因是某一个因素的概率

  • 期望 E(x) :事件A有多种结果,记其结果为x,那么x的期望值表示事件A结果的平均大小

    竞赛中,大多数是求一个离散型随机变量X的期望,如果X的输出值为xi,对应概率为Pi,那么

    E(X)=Pixi

    性质:

    • E(x+c)=E(x)+c
    • E(cx)=cE(x)
    • 线性性质:记两个事件的结果为x,y,那么E(x+y)=E(x)+E(y)E(xy)=E(x)E(y)这个式子成立要求x,y相互独立

应用

概率与期望常常和其他算法一起使用 (比如dp)

概率的一般套路就是

dpi=(dpk×P)

期望的一般套路就是根据定义:

dpi=dpi1+vali()×Pi

注意这里的val可能是dp数组

Bag of mice

click

上来就dp

定义dpi,j表示袋子里有i只白鼠,j只黑鼠时公主赢的概率

分类讨论转移

  • 公主抓到白鼠,公主直接赢

dpi,j+=ii+j

  • 公主抓到黑鼠,该龙了
    但龙不能赢,所以龙抓到黑鼠
    这两个事件相互独立,所以同时发生的概率是P(AB)=ji+j×j1i+j1
    接下来会有老鼠跑掉
    • 跑出白鼠,也就相当于抓一只白鼠放生

    P(ABC1)=P(AB)P(C1|AB)=ji+j×j1i+j1×ii+j2

    那么该事件发生后此时公主赢的概率就是老鼠抓完和跑掉后剩余老鼠对应情况的赢的概率,所以

    dpi,j+=P(ABC1)×dpi1,j2

    • 跑掉黑鼠,同理

    dpi,j+=P(ABC2)×dpi,j3=ji+j×j1i+j1×j2i+j2

初始化:全是白鼠时公主必赢,没白鼠是龙只要等抓完了就能赢

dp0,i=0,dpi,0=1

for(int i = 1;i <= w;i++) dp[i][0] = 1;
for(int i = 1;i <= b;i++) dp[0][i] = 0;
for(int i = 1;i <= w;i++)
{
	for(int j = 1;j <= b;j++)
	{
		dp[i][j] += (double) i / (i + j);
		if(j >= 3)//防止越界
			dp[i][j] += (double)dp[i][j - 3] * j / (i + j) * (j - 1) / (i + j - 1) * (j - 2) / (i + j - 2);
		if(i >= 1 && j >= 2)
			dp[i][j] += (double) dp[i - 1][j - 2] * j / (i + j) * (j - 1) / (i + j - 1) * i / (i + j - 2) ;
	}
}
printf("%.9lf",dp[w][b]);

Jon and Orbs

click

翻译又是依托答辩

意思就是有k种物品,问什么时候取遍每种物品的概率符合题意(有些天可能抽到同一种物品)

dpi,j表示过了i天,取了i件,取遍了j种的概率

要从前一天转来,考虑第i天取的东西的种类

  • 取的物品种类先前已出现,P=jk

dpi,j+=P×dpi1,j

  • 取得东西种类先前未出现,那么前i1天只取遍了j1种,新种类有k(j1)种,P=nj+1k

dpi,j+=P×dpi1.j1

初始化:dp0,0=1

dp完后按要求找答案即可

dp[0][0] = 1;
for(int i = 1;i <= 10005;i++)//也不知道是第几天,多弄点
{
	for(int j = 1;j <= x;j++)
	{
		dp[i][j] += (double) j / x * dp[i - 1][j];
		dp[i][j] += (double) (x - j + 1) / x * dp[i - 1][j - 1];
	}
}

P1365 WJMZBMR打osu! / Easy

click

还是dp,一道期望dp

考虑到啊期望的计算还和连续o的长度有关,所以需要再开一个数组记录长度(的期望)

fi表示到了第i位的总期望,gi表示到了第i位的期望长度

对于长度为k的连击序列,如果下一位还是o,那么该位的贡献就是(k+1)2k2=2k+1

为了方便,我们规定在g中,每一位的贡献是1

如果第i位能和前面连起来,说明第i位是o的概率为1,那就是

gi=E()=E(o)+E(o)=1×1+gi1=gi1+1

转移:

  • si==x,说明对于这一位,贡献为0的概率为1,和前面的连击连起来,即gi1+1的概率是0,那么

    gi=E(i)=0×1+0×(gi1+1)=0

    此时

    fi=E(i)+E(i1)=0×(2gi1+1)+fi1=fi1

  • si=o,类比上面的计算,发现贡献E(i)中乘的概率是1,所以得到

    fi=fi1+2gi1+1,gi=gi1+1

  • si=?,乘的概率变为0.5

fi=0.5(2gi1+1)+fi1gi=0.5(gi1+1)

答案:fn

for(int i = 1;i <= n;i++)
{
	if(s[i] == 'x')
	{
		f[i] = f[i - 1];
		g[i] = 0;
	}
	if(s[i] == 'o')
	{
		f[i] = f[i - 1] + 2 * g[i - 1] + 1;
		g[i] = g[i - 1] + 1;
	}
	if(s[i] == '?')
	{
		f[i] = 0.5 * (2 * g[i - 1] + 1) + f[i - 1];
		g[i] = 0.5 * (g[i - 1] + 1);
	}
} 
printf("%.4lf",f[n]);

来看看他的兄弟:

P1654 OSU!

click

这里的单点贡献变成了(x+1)3x2=3x2+3x+1,每次乘的概率变成了pi

其中,3x2这一部分可以利用上一道题的结论维护

int n;
double a[N];
double f[N],g[N],h[N];
// g:x^2  h:x
int main()
{
	scanf("%d",&n);
	for(int i = 1;i <= n;i++) scanf("%lf",&a[i]);
	for(int i = 1;i <= n;i++)
	{
		double u = 3 * g[i - 1] + 3 * h[i - 1] + 1;//单点贡献 
		f[i] = a[i] * u + f[i - 1];
		g[i] = a[i] * (g[i - 1] + 2 * h[i - 1] + 1);//上一题结论 
		h[i] = a[i] * (h[i - 1] + 1);//x的维护 
	} 
	 printf("%.1lf",f[n]);
	return 0;
} 

那能不能不用上一题的结论呢

double u = 3 * h[i - 1] * h[i - 1] + 3 * h[i - 1] + 1;//单点贡献 
f[i] = a[i] * u + f[i - 1];
h[i] = a[i] * (h[i - 1] + 1);//x的维护 

很遗憾,这样是错的

简单理解就是:期望的转移只满足线性性质(类似一次函数),多次方的运算对期望来说是不成立的

或者说,期望相乘需要满足事件相互独立,能相互独立的肯定不是同一件事件,所以不支持幂运算

P4316 绿豆蛙的归宿

click

这道题拉开了期望dp的经典套路:倒推

dpi表示i点的期望

这道题就是通过dfs把答案汇总到dp1,末节的dpn就是0

转移:

dpi=vi.todpv+edgeval(iv)outi

意即每次累加走到v的贡献× 对应概率,即除以i的出度数

但为什么样例都过不了还能A呀?!?!

不同之处就在于给点打不打vis,我本人觉得这是没必要的操作,因为题目也没规定,要是纯按概率走也会出现重复经过点的情况

就挺奇妙

void dfs(int x)
{
	if(x == n)
	{
		dp[n] = 0;
		return;
	}
	//if(vis[x]) return;
	//vis[x] = 1;
	for(int i = head[x];i;i = e[i].next)
	{
		int k = e[i].to;
		dfs(k);
		dp[x] += (dp[k] + 1.0 * e[i].val) / out[x];
	}
}

P1297 [国家集训队] 单选错位

click

规定每个选择做对的贡献都是1

题意即为第i道题的正确答案飞到了第i+1道题上求作对概率和(期望每次的累加就是1k,相当于概率和),此时第i+1题上的答案范围就是[1,ai]

那么,在这种情况下做对第i+1题当且仅当两道题答案一样

一样的结果有min(ai,ai+1)种,总结果有ai×ai+1种,因此

ans=min(ai,ai1)ai1×ai=1max(ai,ai1)

坑点:还有a1,an要比较

double ans = 0;
for(int i = 2;i <= n;i++) ans += (double)1.0 * 1 / (max(a[i],a[i - 1]));
ans += (double) 1 / (max(a[1],a[n]));//不能忘
printf("%lf",ans); 

[bzoj1419]Red is good

click

dpi,j表示当前剩下i张红牌,j张黑牌时的期望

  • 不翻,获得分数的概率为0dpi,j=0

  • 翻,那么就是翻到红牌的期望和翻到黑牌的期望之和
    此时翻到红,黑牌的概率Pr=ii+jPb=ji+j,贡献题目有说,是1/1
    此时

    dpi,j=dpi1,j×Pr+dpi,j1×Pb

上述两种情况取max即可

卡空间的话滚动一下就好

初始化:dpi,0=i,因为这时咋抓都是得分,故此时的期望就是l=1i(1×Pr(=1))=i

坑点:保留时不四舍五入,要利用整型“断尾”特点来搞

double init(double x)
{
	int u = x * 1000000;
	return u * 1.0 / 1000000;
}
for(int i = 1;i <= r;i++)
{
	dp[i % 2][0] = 1;
	for(int j = 1;j <= b;j++)
	{
		//dp[i][j] += dp[i - 1][j - 1];
		dp[i % 2][j] = max((d)(dp[i % 2][j - 1] - 1.0) * j / (i + j) + (dp[(i - 1) % 2][j] + 1.0) * i / (i + j),0.0);
	}
}
	答案:dp[r][b]

或者还可以定义为拿了i,j张牌,这时就是倒推,但原理相同

for(int i = x;i >= 0;i--)
{
	dp[i % 2][y + 1] = r - i;
	for(int j = y;j >= 0;j--)
	{
		dp[i % 2][j] = max(0.0,(dp[(i + 1) % 2][j] + 1) * 1.0 * (r - i) / (r + b - i - j) + (dp[i % 2][j + 1] - 1) * 1.0 * (b - j) / (r + b - i - j));
	}
}
	答案:dp[0][0]

[bzoj2720][Violet 5]列队春游

click

为了方便,可以先对数据排序,方便获得有多少人比他高/矮

对于第i个人,我们尝试计算他的贡献及对应的概率

假设现在求第i个学生(枚举的是数值不是位置)的视野(自身和比他矮的,算上自身是为了乘贡献时方便)为k,那么k[1,n]

再设身高不低于第i个学生的人数为H(i)

那么,达到贡献k有两种方法

  • 第一个比他高的是老师
    这时1k1()的人都比第i个人要矮,排列方案有AnHi1k1,总数为Ank

    ans+=k×AnHi1k1Ank

  • 第一个比他高的是同学
    这时要求kn1,因为k=n时只能是老师比他高

    那么这就要满足

    • ik+1i1(k1) 都比他矮
    • ik处的人身高不低于他

    实现第一个限制的方案数为AnHi1k1,实现第二个方案的方案数是Hi,这时涉及到的区间长度是i(ik)+1=k+1,所以总情况是Ank+1

    又因为这个长度为k+1的区间可以左右移动,每一个不同的位置都是一个上述情况,而n中一共有n(k+1)+1=nk个这样的区间,所以还要乘一个nk

    ans+=k×(nk)×Hi×AnHi1k1Ank+1,k[1,n1]

    枚举i,k统计答案即可

double ans = 0;
for(int i = 1;i <= n;i++)
{
	for(int j = 1;j <= n;j++)
	{
		ans += j * A[n - h[i] - 1][j - 1] / A[n][j];//老师贡献 
		if(j <= n - 1)
			ans += (double)j * (n - j) * h[i] * A[n - h[i] - 1][j - 1] * 1.0 / A[n][j + 1];//学生贡献 
	}
}
//上面的码子会输出-nan,得用一种叫long double的东西
//由于本蒟蒻不知道对应的printf类型,还得用神秘setpresicion(2)

O(n2) 开开科技卡精度防-nan能过

法二:O(n)

好像是能把一个i的期望和变成所有可能的k对应的概率之和

意思就是

E(i)=k=1nPk

但我不会,想不到E(i)里面乘的权值是怎么省略的

烦了,直接暴力一下

法一可写成

j=1nj×AnHi1j1Anj+j=1n1j(nj)HiAnHi1j1Anj+1

注意到前面的中若j=n,只有当Hi=0时结果为1,其余均为0,而Hi=0也只能有一个(最高且没有相同身高的人),单拎出来该情况,后续处理

所以接下来所有Hi1j=n时就是0

那么写成

j=1n1j×AnHi1j1Anj+j=1n1j(nj)HiAnHi1j1Anj+1

化简(式中Hi1)

=j=1n1j×AnHi1j1Anj+1nj+j=1n1j(nj)HiAnHi1j1Anj+1=j=1n1j(nj)AnHi1j1Anj+1+j=1n1j(nj)HiAnHi1j1Anj+1=j=1n1(j(nj)AnHi1j1Anj+1+j(nj)HiAnHi1j1Anj+1)=j=1n1(j(nj)AnHi1j1(Hi+1)Anj+1)=j=1n1(j(nj)(nHi1)!(nHij)!(Hi+1)n!(nj1)!)=j=1n1(j(nj)(nHi1)!(Hi+1)(nj1)!n!(nHij)!)=(nHi1)!n!(Hi+1)j=1n1(j(nj)!(nHij)!)=(Hi+1)(nHi1)!n!j=1n1(jCnjHi×Hi !)=(Hi+1)!(nHi1)!n!j=1n1jCnjHi

接下来对于j=1n1jCnjHi

考虑到j=nHi1,C0Hi=0,所以

j=1n1jCnjHi=j=1njCnjHi

这时想起

Tn=i=1n(ifi)

Tn=f1+2f2+3f3++nfn=nSnSn1Sn2S1=nSni=1n1Si

这时矩阵里的trick,这里可以类比使用

定义Si=k=1iCnkHi

那么原式子就是nSni=1n1Si

现在化简Si

考虑公式

Cnm=Cn1m+Cn1m1

上式末尾是一个CniHi,补一个+CniHi+1CniHi+1,合并得到

Si=CnHi+1CniHi+1

带入:

=n(CnHi+1C0Hi+1(=0))(n1)(CnHi+1)+Cn1Hi+1+...+C1Hi+1+C1Hi+2(0)=CnHi+1+CnHi+2=Cn+1Hi+2

所以

E(i)=(Hi+1)!(nHi1)!n!Cn+1Hi+2=(Hi+1)!(nHi1)!(n+1)!n!(Hi+2)!(n+1Hi2)!=n+1Hi+2

一般情况化简完毕,考虑Hi=0

首先期望的表达式要补1

j=1n1j×AnHi1j1Anj+j=1n1j(nj)HiAnHi1j1Anj+1+1

其次,组合化简时nC00=n,所以

j=1n1jCnjHi=j=1njCnjHin

其他一致,带入

=(Hi+1)!(nHi1)!n!(j=1njCnjHin)+1=(Hi+1)!(nHi1)!n!(Cn+1Hi+2n)+1=(Hi+1)!(nHi1)!n!Cn+1Hi+2(0+1)!(n01)!n!×n+1=n+1Hi+2(1n×n1=0)

同样符合结论

至此总结,可以得到

ans=i=1nE(i)=i=1nn+1Hi+2

O(n)算法得证

暴力算完之后又把直接推出的方法看了看

先前计算贡献为k时的期望,相当于限定了区间长L只能是k并且这k个人紧挨着i

E(i)=k=1nk×Pk (L=k)

而实际上连续的k个人也能在Lk时产生贡献,此时他们不一定连着i,可能只是长为L的视野中的一部分,那么这样就相当于把原来要乘的贡献拆成了一个一个小段,每个小段的权值都是1,那么就变成了

E(i)=k=1nPk

这里,由于k个人不一定连着i,概率的计算方式要改变

不变的是 k个人和i的相对关系

考虑到沿用先前的定义,这里把k重定义为k个比i高的人,那么k就是Hi

k个人和i自由排列方案是Ank+1

考虑到相对关系不变,即那k个人只能在i后面,即ni个位置,方案Anik

又因为 i不一定连着k,所以放置位置为[0,ni](把老师也算进去),有ni+1种选择

所以

E(i)=k=1n(ni+1)AnikAnk+1

化简从略,肯定比上面简单些,但结果是一样的

[bzoj2969]矩形粉刷

click

首先每次选的矩形的尺寸不好把握,再结合期望的线性,可以计算单个方格的期望,又因为一个方格的贡献就是1(面积为一,且只会产生一次贡献,即被刷上之后不再产生贡献),所以就是概率和

但正是因为一个格子只会被上一次色,我们难以得知这一次是k次中的哪一次,所以不妨求得该格子k次都不会被刷到的概率,拿1减一下就好了

设格子坐标为(x,y)

那么就要保证该点不在选择的两点确定的矩形中,因此选的两点在该点的同侧(上,下,左,右)

看图

设算出的概率为P,那么

又因为连续选k次,上面算的还是未刷上,所以一个点的贡献是1Pk

求和即可,(1Pk)

坑点包括:

  • double写成int
  • 不考虑平方偷懒合并上面的项(比如前四项并成(W1)H+(H1)W
  • 算成(1P)k,(1P)k,1Pk
double init(double x)//这里也是double哦
{
	return x * x;
}
int main()
{
	int k,w,h;
	scanf("%d%d%d",&k,&w,&h);
	double ans = 0;
	for(int i = 1;i <= w;i++)
	{
		for(int j = 1;j <= h;j++)
		{
			int x = j,y = i; 
			double kk = ((init((w - y) * h) + init((y - 1) * h) + init((x - 1) * w) + init((h - x) * w) - init((x - 1) * (w - y)) - init((h - x) * (w - y)) - init((x - 1) * (y - 1)) - init((h - x) * (y - 1))) * 1.0 / init((w * h)));//图中算法
			//cout << kk << endl;
			ans += 1 - pow(kk,k); 
		}
	}
	printf("%.0lf",ans);
	return 0;
}

守卫者的挑战

click

应该是dp,按照囊括要素N,L,k的方法定义

定义dpi,j,k表示挑战了i场,成功了j次,当前背包容量为k

如果这一次失败了,继承上一次状态

dpi,j,k=dpi1,j,k(1Pi)

如果成功了,那就是

dpi,j,k=dpi1,j1,kai×Pi

这个方程对ai=1也同样适用

看到第三维会有一个问题:负数是否成立

回到题目

只需要完成所有N项挑战后背包容量足够容纳地图残片即可

也就是说,只要求终末状态的背包容量不为负数,中间转移时装不下了不要紧,可以等新背包出现

那么就要防止越界,启动学校食堂 (不是饿了是那道状压题)

有一个小问题:k2000,ai1000,好像开不下

但事实上最多得到n块碎片,也就是说有用的空间最多只有n,防越界的话都加上一个n即可

那么,原先的负数域对应的就是[0,n1],非负数对应的就是[n,2n]

考虑到dp式中有lai,但ai可能过大不好操控,因此把dp式写成ii+1的形式,,转移时求min舍掉冗余空间即可

答案就是dpn,ln,n2n

初始化:dp0,0,k+n=1

坑点:写成ii+1形式后答案第一维会变成n+1

卡空间的话滚动即可

k = min(k,n);//舍掉冗余
dp[0][0][n + k] = 1;
for(int i = 0;i <= n;i++)
{
	for(int j = 0;j <= n;j++)
	{
		for(int l = 0;l <= n * 2;l++)
			dp[(i + 1) % 2][j][l] = dp[i % 2][j][l] * 1.0 * (1 - p[i]);
	}
	for(int j = 0;j <= n;j++)
	{
		for(int l = 1;l <= n * 2;l++)
		{
			int kk = min(l + a[i],2 * n);//舍掉冗余
			dp[(i + 1) % 2][j + 1][kk] += dp[i % 2][j][l] * 1.0 * p[i];
		}
	}
}
double ans = 0;
for(int i = l;i <= n;i++)
{
	for(int j = 0;j <= n;j++)
	{
		ans += dp[(n + 1) % 2][i][j + n];//第一维变成n + 1
	}
}
printf("%.6lf",ans);

P4206 [NOI2005] 聪聪与可可

click

题目的关键是这两步其实算一个时刻,而题目更确切的是在求时刻的期望

对于样例

解析:


结果就是34×2+112×2+212×3=2+16=2.167

首先猫所谓的“离老鼠最近的景点”应该涉及到最短路,可以预处理

void init()
{
	for(int i = 1;i <= n;i++)for(int j = 1;j <= n;j++) d[i][j] = 2005;
	for(int i = 1;i <= n;i++)
	{
		dijkstra(i);
		for(int j = 1;j <= n;j++) dt[i][j] = dt[j][i] = dis[j].w;
		dt[i][i] = 0;
	}//处理出任意两点间的最短路
	for(int i = 1;i <= n;i++)
	{//枚举老鼠
		for(int j = 1;j <= n;j++)//枚举猫
		{
			for(int u = 0;u < e[j].size();u++)//枚举猫可能的下一步
			{
				int v = e[j][u];
				if(dt[v][i] == dt[j][i] + 1)//如果枚举的落脚点在i,j间的最短路上
				{
					d[v][i] = min(d[v][i],j);//还要注意标号最小
				}
			}
		}
	}
}

定义di,j表示当前猫在i点,老鼠在j点时猫的选择

那么如果同一时刻内猫要走两步,下一步就是ddi,j,j

定义dpi,j表示猫在i,老鼠在j处时的期望步数

那么,猫鼠在同一个点时dpi,j=0

答案的累加考虑使用dfs的回溯实现

规定猫走一个时刻的权值为1

分类讨论:

  • 猫能在自己的回合内吃到老鼠

    di,j=jddi,j,j=j,那么dpi,j=1(百分之百抓到老鼠)

  • 猫不能吃到

    ddi,j,j=k,kj

    那么此时轮到老鼠走了,设老鼠:jl,那么新情况就是dpk,l,概率是1outj,可以使用dfs累加,即

    dpi,j=jl(dpk,l×1outj)

    注意这里l还能是j

写出代码

double dfs(int mao,int shu)
{
	if(mao == shu) return (dp[mao][shu] = 0);
	int s1 = d[mao][shu];
	int s2 = d[s1][shu];
	if(s1 == shu) return (dp[s1][shu] = 1);
	if(s2 == shu) return (dp[s2][shu] = 1);
	for(int i = 0;i < e[shu].size();i++)
	{
		int k = e[shu][i];
		dp[mao][shu] += dfs(s2,k) * 1.0 / (out[shu] + 1);
	}
	dp[mao][shu] += dfs(s2,shu) / (out[shu] + 1);
	return dp[mao][shu];
}

发现又WATLE

WA是因为没谈到初始化状物

对于初始化,dfs中的两个连续的if就是在模拟猫走的过程,那么if完了之后猫就走过了,此时权值要加1

对于TLE,可以使用记忆化思想,给走过的点打vis,如果访问过了直接return即可

double dfs(int mao,int shu)
{
	if(ifvis[mao][shu]) return dp[mao][shu];//走过了直接return省时间
	if(mao == shu) return (dp[mao][shu] = 0);
	int s1 = d[mao][shu];
	int s2 = d[s1][shu];
	if(s1 == shu) return (dp[s1][shu] = 1);
	if(s2 == shu) return (dp[s2][shu] = 1);//模拟猫的走法
	dp[mao][shu] += 1;//猫走完了
	for(int i = 0;i < e[shu].size();i++)
	{
		int k = e[shu][i];
		dp[mao][shu] += dfs(s2,k) * 1.0 / (out[shu] + 1);
	}//累加老鼠的走法
	dp[mao][shu] += dfs(s2,shu) / (out[shu] + 1);//别忘了还可能留在原地
	ifvis[mao][shu] = 1;//打标记
	return dp[mao][shu];//返回答案
}

P2059 [JLOI2013] 卡牌游戏

click

只有一个人的时候最简单,百分之百赢

如果有两个人,那获胜概率就是奇数/偶数的占比,这取决于谁坐庄。

如果有三个人,可以通过让一个人毙业进入只有两个人的状态,这启示我们可以尝试使用dp

先设dpi表示当前剩了i个人,方向是i1i

考虑到题目要求所有人的胜率,所以还得加一维枚举人头

再定义dpi,j表示场上有i个人,第j个人的胜率

这里要注意的是由于人毙业后其他人的相对位置会改变,所以这里的第j个表示从新庄家开始数第j个人,为了方便,后面的第..个均是从庄家开始数

假如抽到了a,那么毙业的就是第a%i个人,设为k

那么此时k+1变为庄家,则对于原先第j个人,如果k<j,那么他的新位置就是j(k+1)+1=jk ,否则就是ik+j。这样做是为了保证一个人的胜率转移是连续的

一共有m种抽牌情况,那么每次毙业的概率就是1m,所以得到

dpi,j=l=1m1mdpi1,num

num={jk (k<j)ik+j (k>j)

最后每个人的胜率就是dpn,i

初始化:最前面提到的百分百获胜:dp1,1=1

dp[1][1] = 1;
for(int i = 2;i <= n;i++)
{
	for(int j = 1;j <= i;j++)
	{
		for(int l = 1;l <= m;l++)
		{
			int k = a[l] % i;
			if(k < j) dp[i][j] += dp[i - 1][j - k] * 1.0 / m;
			if(k > j) dp[i][j] += dp[i - 1][i - k + j] * 1.0 / m;
		}
	}
}

P1850 [NOIP2016 提高组] 换教室

click

最短路什么的预处理就好了的说

for(int k = 1;k <= v;k++)
	for(int i = 1;i <= v;i++)
		for(int j = 1;j <= v;j++)
			dis[i][j] = min(dis[i][j],dis[i][k] + dis[k][j]);//floyd的矩阵存图方便后期dp式的表示

囊括要素定义dp:设dpi,j,1/0表示当前在时刻i,申请了j次,第i个时段有/没有申请(因为题目说了甚至可以一次也不申请)时的期望

根据期望定义,=+×

大力分类讨论

  • 没申请第i课时
    • 没申请第i1课时,那么前后两次百分之百是在原来的教室,即ci,ci1

      dpi,j,0=dpi1,j,0+dis(ci,ci1)

    • 申请了第i1课时,但是不一定能申请成功,要乘概率的,申请成功的概率为pi1,不成功的话拿1减一下就好了

      dpi,j,0=dpi1,j,1+pi1×dis(ci,di1)+(1pi1)dis(ci,ci1)

上述情况比个min

  • 申请了第i课时
    • 没申请第i1课时,那么只可能是cidi

      dpi,j,1=dpi1,j1,0+pi×dis(di,ci1)+(1pi)dis(ci,ci1)

    • 申请了第i1课时,共计四种情况
      • 均成功

        val1=pi×pi1×dis(di,di1)

      • 只成功一个,要么前成后不成,要么前不成后成

        val2=(1pi)pi1×dis(ci,di1)

        val3=pi(1pi1)dis(di,ci1)

      • 均不成功

        val4=(1pi)(1pi1)dis(ci,ci1)

      那么

      dpi,j,1=dpi1,j1,1+val1+val2+val3+val4

上述情况取min

初始化:dpi,0,0=dpi1,0,0+dis(ci,ci1),就是申请前的最短路

坑点:

  • dpi,0,1并不存在,这种情况就没必要(也不能)做第二大情况的转移(不写的话44pts)
  • 可能一次也不申请才是最优解(不写的话76pts
  • 不能用memset(写了的话20pts)
for(int i = 1;i <= n;i++) for(int j = 1;j <= m;j++) dp[i][j][0] = dp[i][j][1] = inf;// No memseting
for(int i = 1;i <= n;i++) dp[i][0][0] = dp[i - 1][0][0] + dis[c[i]][c[i - 1]];//初始化
for(int i = 1;i <= n;i++)
{
	for(int j = 1;j <= i;j++)
	{
		int c1 = c[i],c2 = c[i - 1],d1 = d[i],d2 = d[i - 1];
		dp[i][j][0] = min(dp[i][j][0],dp[i - 1][j][0] + dis[c1][c2]);
		dp[i][j][0] = min(dp[i][j][0],dp[i - 1][j][1] + 1.0 * p[i - 1] * dis[c1][d2] + 1.0 * (1 - p[i - 1]) * dis[c1][c2]);
		dp[i][j][1] = min(dp[i][j][1],dp[i - 1][j - 1][0] + 1.0 * p[i] * dis[d1][c2] + 1.0 * (1 - p[i]) * dis[c1][c2]);
		if(j == 1) continue;//卡掉没必要申请的情况
		double num1 = 1.0 * p[i] * p[i - 1] * dis[d1][d2];
		double num2 = 1.0 * p[i] * (1 - p[i - 1]) * dis[d1][c2];
		double num3 = 1.0 * (1 - p[i]) * p[i - 1] * dis[c1][d2];
		double num4 = 1.0 * (1 - p[i]) * (1 - p[i - 1]) * dis[c1][c2];
		dp[i][j][1] = min(dp[i][j][1],dp[i - 1][j - 1][1] + num1 + num2 + num3 + num4);
	}
}
double ans = inf; 
ans = min(ans,dp[n][0][0]);//没申请
for(int i = 1;i <= m;i++) 
{
	ans = min(ans,min(dp[n][i][0],dp[n][i][1]));
}
printf("%.2lf",ans);

P2473 [SCOI2008] 奖励关

click

n15,状压(CaO)

套路的先写出dpi,S,1/0,表示当前是第i次吃宝物,此时吃的宝物种类状态是S(每一位表示吃没吃过),这一次吃还是不吃

处理每个宝物的前置状态不消说

然后发现要吃的话如果不满足前置状态也吃不了,相当于没吃,好像第三维没什么卵用,也就是说吃还是不吃只在能吃的情况下抉择,不是统一情况,这种东西放到比max里实现

如果第i次吃了宝物j,那么

Si|(1<<j1)=Si+1

这里启示我们可以使用倒序,即

dpi+1,Si|(1<<j1)dpi,Si

如果放弃吃,那么两次状态一样

dpi,Si=dpi+1,Si

如果要吃,那就选择吃不吃

dpi,Si=max(dpi+1,Si,dpi+1,Si|(1<<j1)+P×valj)P=1n

枚举i,Si,j,复杂度O(k2nn)在承受范围内

写出代码:

for(int i = k;i >= 0;i--)
{
	for(int s = 0;s <= (1 << n) - 1;s++)
	{
		if(check(s) > i) continue;//保证吃到的种类数不超过抽奖次数
		for(int j = 1;j <= n;j++)
		{
			int news = 1 << (j - 1);
			if(check(s | news) > i + 1) continue;
			if((s & a[j]) != a[j]) dp[i][s] += dp[i + 1][s];
			else dp[i][s] += max(dp[i + 1][s],dp[i + 1][s | news] + 1.0 * P * val[j]);//上面的式子
		}
	}
}
printf("%.6lf",dp[0][0]);

发现错得离谱

想了想发现道理也不复杂:抽到物品j的概率是P,也就是说继承对应的期望的概率也是P,这表明dp数组也得乘以概率,和上面题目中dfs的累加十分相似

for(int i = k - 1;i >= 0;i--)//细节:0~ k是k + 1次,要实现k次是0~k-1
{
	for(int s = 0;s <= (1 << n) - 1;s++)
	{
		for(int j = 1;j <= n;j++)
		{
			int news = 1 << (j - 1);
			if((s & a[j]) != a[j]) dp[i][s] += dp[i + 1][s] * P;
			else dp[i][s] += max(dp[i + 1][s] * P,dp[i + 1][s | news] * P + 1.0 * P * val[j]);//全部乘以概率 
		}
	}
}
//这里原先的check会带小常数增加TLE风险,其实卡掉的那一部分不影响dp,所以可以删掉

答案就是dp0,0,无需初始化

那么想一想,什么时候dp数组也要乘以概率?期望应该是只有新增的权值才乘以概率的呀?

很简单:条件概率模型

也就是说,只有当获得新权值的情况成为继承期望的“前提”时才要给期望乘以概率,此时继承期望就是“在该条件下发生的事件”

这样一来前面的都说的通了

dfs式期望很显然前一步是后一步的前提,比如猫抓老鼠中累加某点期望的前提就是老鼠走到那个点,所以一般都要乘以概率

而换教室之类就不一样,当前一步换不换教室和继承上一步的期望没有任何关联,是独立的,此时期望不乘以P

P4284 [SHOI2014] 概率充电器

click

(煞笔吧充电还看脸)

树形的,新品种

规定一个元件充上电的权值为1,那么期望就是每个节点充上电的概率的和

dpi表示i点亮的概率

一个点充上电有三种可能:

  • 自己充
  • 被父亲充
  • 被儿子充

第一条的实现就是初始化

后面两条如果同时考虑会很混乱(父亲充儿子不充,儿子充父亲不充,俩一块儿充,俩都不充...,而且这中间还可能相互重叠),所以考虑dfs两遍

由于一个点只能有一个父亲,但可能有一堆儿子,而且儿子之间相互独立。所以我们考虑先把儿子充的概率累计后在计算父亲充的概率

使用公式

P(AB)=P(A)+P(B)P(AB)

对于节点x和他的一个儿子son,由公式可得

P(x)=P(x)+P(x)P()

就是

Px(dpx)=dpx0+dpson0×edgeP(x,son)dpx0×dpson0×edgeP(x,son)

现在的dp数组存的是x及其所有儿子的共同努力下x的概率(注意,是所有),也就是式中的Px,不妨记为dpi1

考虑父亲,把更新后的数组(答案)记为dp2

还是利用公式:

P(x)=P(x)+P(fax)P()

这里有个小细节:如果要靠父亲充x那么父亲亮就不能靠x,只能靠自己和其他儿子,把刨掉x后的dpfa记作dpx

dpx2=dpx1+dpfax×edgeP(fa,x)dpx1×dpfax×edgeP(fa,x)

接下来要想办法求解dpx

我们回到汇总儿子充的计算式中,当xfa的儿子时,套用上式并结合dp数组含义可得:

dpfa1=dpfax+dpx1×edgeP(fa,x)dpfax×dpx1×egdeP(fa,x)

=+x

可以解得

dpfax=dpfa1dpx1×edgeP(fa,x)1dpx1×edgeP(fa,x)

回带入答案式dfs即可

坑点见代码

void dfs(int x,int fa)//汇总儿子的答案
{
	for(int i = head[x];i;i = e[i].next)
	{
		int k = e[i].to;
		if(k == fa) continue;
		db num = e[i].w;
		dfs(k,x);
		dp[x] = (dp[x] + dp[k] * num - dp[x] * dp[k] * num); 
	}
}
void getans(int x,int fa)//此时存的都是dp1
{
	for(int i = head[x];i;i = e[i].next)
	{
		int k = e[i].to;
		db num = e[i].w;
		if(k == fa) continue;
		if(fabs(1 - dp[k] * num) > 1e-7)//坑1:不能除以0(极小值)
		{
			db dp_x = (dp[x] - dp[k] * num) * 1.0 / (1 - dp[k] * num);//求解dp_x
			dp[k] = dp[k] + dp_x * num - dp[k] * dp_x * num;
		}
		getans(k,x);//坑2:别手滑写成dfs(doge)
	}
}
int main()
{
	scanf("%d",&n);
	for(int i = 1;i <= n - 1;i++)
	{
		int u,v;
		db s;
		scanf("%d%d%lf",&u,&v,&s);
		add(u,v,s / 100.0);
		add(v,u,s / 100.0);//坑3:记得化成小数
	}
	for(int i = 1;i <= n;i++) 
	{
		int y;
		scanf("%d",&y);
		dp[i] = val[i] = 1.0 * y / 100.0;//初始化为点亮自己的概率
	}
	dfs(1,0);
	getans(1,0);
	db ans = 0;
	for(int i = 1;i <= n;i++) ans += dp[i];
	printf("%.6lf",ans);
	return 0;
}

P3232 [HNOI2013] 游走

click

想到Piggies(巧了也是概率)

可以得到

E=vale×Pe

对于计算Pe,就是与之相连的点i1outi的概率经过它,再考虑到可能经过多次,所以是点的期望经过次数和概率的乘积

接下来考虑计算点的经过次数(期望),结合联想到的题目和前面的概率题,可知使用n元一次方程组和Gaussian Elimination

设点i的期望经过次数为xi,仿照Piggies可知

xi=xsonoutson(edge(i,son)E)

考虑特殊点:

对于点1,由于是起点,所以多一次经过

x1=1+xsonoutson(edge(1,son)E)

对于点n,由于百分百到n并且不会由n走向其他点,所以上述方程中均不考虑n

那么对于一条边,要么从左端点过来,要么从右端点过来

edgeP(i,j)=xiouti+xjoutj

排序,让最小的概率乘最大的编号并累加即可

注:从概念上来说edgeP指的是一条边的期望经过次数,但是由于经过一次的贡献只有1,所以数值上等于概率和

//前面有n--
a[1][n + 1] = -1; 
for(int i = 1;i <= n;i++)
{
	a[i][i] = -1;
	if(vis[1][i]) a[1][i] = 1.0 / out[i];
}
for(int i = 2;i <= n;i++)
{
	for(int j = 1;j <= n;j++)
	{
		if(i == j) continue;
		if(vis[i][j]) a[i][j] = 1.0 / out[j];
	}
}//弄系数矩阵
gauss();//Elimination
for(int i = 1;i <= m;i++) 
{
	int u = e[i].from;
	int v = e[i].to;
	e[i].Ex = a[u][n + 1] * 1.0 / out[u] + a[v][n + 1] * 1.0 / out[v];
}//求边的概率
sort(e + 1,e + m + 1,cmp);//从大到小排序
double ans = 0;
for(int i = 1;i <= m;i++) ans += i * e[i].Ex;//小编号*大概率

然而70pts

又看了看参考题目发现是eps精度不够,得开1e10及以下

6

P3211 [HNOI2011] XOR和路径

click

让我先捋捋重边和自环什么鬼

好像不影响

这应该是个异或方程组

xi=edge(i,s)ExsedgeVal(i,s)outs

发现这式子没法解,需要分离+的操作

加减换成异或不太科学,考虑用加减模拟异或

那么这里就有了一个骚操作:把数拆成一位一位来搞,加减0/1,因为位运算的各个位是互不影响的

那么改变xi的含义:i点出发走到n点,使得第i位为1的概率,对应的,edgeVal{0,1},此时他代表边权的第i

还有一点就是

outiis

xnnother

接下来考虑如何模拟

由于现在存的是位,那么除法不好实现,先乘过去

outi×xi=xsedgeVal(i,s)

下面用vals表示edgeVal(i,s)

结合位运算的性质,可得

outi×xi=edge(i,s)E(xs!=vals)

但是为了使用高斯消元需要把这种式子写成方程的形式

vals=0时,只有xs=1才能贡献1,那么累加的话可以直接累上去(反正+0没影响),那么当只有xs=0时才贡献1,就可以用1减一下

outi×xi=vals=0xs+vals=1(1xs)

进一步变形就是

outi×xivals=0xs+vals=1xs=vals=11

注:两个ifelse的关系

那么如何汇总答案呢

根据xi的定义,可知答案就都存在x1里,那么

ans+=2numx1num

P3750 [六省联考 2017] 分手是祝愿

click

首先这种开关灯的题还是有些基本套路的

  • 操作有周期性,每个灯操作一遍就行了

这道题的限制多了一个尿性:

  • 操作i号灯时,只有编号i的灯才会受到影响

这就表明:把右边大编号区间搞掉后就只用管左边小区间了

要实现这个,最直接的就是从右往左,遇到亮的就按一下,这样走到ii后面的肯定都灭了

有了这个策略以后就可以得到部分分:k=n时哪怕就按照上述操作硬搞,最坏也是每个灯按一下,操作次数=nk成立,不用随机 ,模拟就行 (竟然80pts

int main()
{
	scanf("%d%d",&n,&k);
	for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
	for(int i = n;i >= 1;i--)
	{
		if(a[i])//亮了按一下
		{
			ans++;
			int u = i;
			for(int j = 1;j <= sqrt(u) + 0.5;j++)//更新后面灯的状态
			{
				if(u % j == 0)
				{
					int h = u / j;
					if(j != h) a[j] = !a[j],a[h] = !a[h];
					else a[j] = !a[j];
				}
			}
		}
	}
	for(int i = 1;i<= n;i++) ans = (ans * i) % mod;//按题目说的来
	printf("%lld",ans);
	return 0;
} 

从上面的一点我们大胆猜测操作次数k(不再随机)的区间就是左边的[1,k](但里面的灯的状态可能不是初始态),而此时使用的操作次数最小的方案,就是上面用的那个

接下来考虑一般情况,那就要把随机的部分处理掉

这时就要思考随机操作对前后产生的影响

这时结合性质1:随到亮的时是好方案,没随到就浪费了

从这一点想,可以得到如果不小心把灭的按亮了,肯定要花费一定的步数给他摁回去

那么,如果现在有i盏灯亮着,就会有in的概率按到亮的,变成i1盏亮,有nin的概率按到灭的,如果按到灭的,就变成i+1盏亮着,这时要先变回i盏灯亮的情况,再重复上述过程

这种重复可能无休止在前面遇到过,用的是高斯消元

xi表示当前亮了i盏,要按灭一盏到亮i1时的期望步数,那么

xi=in×1+nin×(xi+1+xi+1)+1

发现每个方程的未知数不像上面图论那样乱重叠,呈现一定的线性。线性加上没乱重叠(无后效性),这个东西“退化”成了dp

dpi=in×1+nin×(dpi+1+dpi+1)

化简

dpi=1+nii(dpi+1+1)=n+(ni)dpi+1i

处理完了随机,还有一点:什么时候跳出随机?

考虑到dp没有使用区间相关物,如果按照上面的猜测,我们无法得知dp何时就进入了[1,k]

那我们不妨再大胆一些:当场上就剩下k盏灯时就可以进行从右到左的扫荡,花费k(感觉很离谱对吧,但猜测就不管那么多了)

这样的话到dpk+1就行了,最后加个k

这里会出现一个小细节:浮点数没法求余,所以边乘边求余不行了

这里可以使用逆元替换dp中的除法

接下来是dp对应细节:

  • 全亮的时候肯定按到亮的,所以

dpn=1

  • 结合dp数组含义,可知:dpi表示的是ii1而不是全局答案,就像吃四个馒头吃饱了,让你饱的是四个馒头不是最后一个馒头,所以

ans=i=k+1n(dpi)+k

最后还有一点:有可能初始态就是可进行最优解态,所以上面的代码留下,ans就是操作次数,如果ansk,就直接输出ans

v[1] = 1;
for(int i = 2;i <= n;i++) v[i] = (ll)(mod - mod / i) * v[mod % i] % mod; 
for(int i = n;i >= 1;i--)
{
	if(a[i])
	{
		sum++;
		int u = i;
		for(int j = 1;j <= sqrt(u) + 0.5;j++)
		{
			if(u % j == 0)
			{
				int h = u / j;
				if(j != h) a[j] = !a[j],a[h] = !a[h];
				else a[j] = !a[j];
			}
		}
	}
}
dp[n] = 1;
for(int i = n - 1;i >= k;i--) dp[i] = (ll)(n + (n - i) * 1.0 * dp[i + 1]) * v[i] % mod;
if(sum <= k) ans = sum;
else
{
	for(int i = k + 1;i <= n;i++) ans = (ans + dp[i]) % mod;
	ans = (ans + k) % mod;
}
for(int i = 1;i <= n;i++) ans = (ans * i) % mod;
printf("%lld",(ans % mod + mod) % mod);

然鹅还是80pts

其中的原因在于 吃的馒头数量不对 累计dpi的范围不对,我们不需要从n个灯亮的情况开搞,事实上,ans所代表的暴力法应当是理论上的最多操作次数,所以上限就是ans

for(int i = k + 1;i <= ans;i++) ans = (ans + dp[i]) % mod;

ps:关于从右往左扫,做题时还只是一个猜想,详细的论证见证明

「SDOI2017」硬币游戏

click

惯性思维干碎一地

看范围知高斯消元

或者认为可能会出现所有人一直都赢不了的无穷态,需要用到高斯消元

设第i个同学赢的概率是xi

先从两个同学的情况入手

设人为A,B,对应串为SA,SB,游戏未结束时摇出来的是S,长度为lenS,lenB,lenA,摇出SA,SB,S的概率为PA,PB,PS

比如SA=THT,SB=TTH

很显然,游戏必然结束时就是SSSA/SSB,但中间可能会提前结束

换句话说,我们不妨计算一下 SSSA/SSB的概率和A,B获胜的概率的关系(不考虑中断)

事实上,再换句话说:A,B的胜率其实是字符串达到某一特定状态的概率

也就是说,我们获得了两种角度来描述字符串的变化:一种是纯摇出来,一种是不断达到A,B要求的特定态,而由于描述的是同一变化,所以可以列含PA,PB的若干方程,从而解得PA,PB

不妨先说SSSA,从纯摇的角度来看达到这一状态的概率是PS×1lenA,例子中就是PS×123SSA=STHT

接下来从A,B的特殊态角度看:

  • STH结尾,那么加上个T就结束了,概率是PA,根据上面说法 就是SST的概率是PA。此时想要达到SSA还得加上两个相应字符,概率是PA×122

  • ST结尾,那么加上THB获胜,概率PB,但是要达到终末态还得再补一个,概率就是PB×12

  • 其他(比如以H结尾),那就加上一个SA,概率是PA

而结合两个角度描述同一变化,我们可以得到

PS×123=PA×122+PB×12+PA

同理,SSSB还有一个方程

PS×123=PA×122+PB

但是一共有三个未知数,还差一个方程

这时候再结合题意可知获胜是互斥的,所以

PA+PB=1

即可求解

接下来我们尝试用字母一般化方程

左边好办,就是多了一个12lenA,结合题意得到左边都是

PS×12m

对于右边,比如是达到A必胜状态的话,我们发现,这与S的后缀能与SA的前缀匹配多少的问题,如果匹配了a长度,那么PA的系数就是12ma,但是对于S的后缀并不好求,那该如何呢?

这里,我们可以借助B来“枚举”S的后缀,大不了就是第三种情况,所以就变成了B的前缀和A的后缀能匹配多少的问题了,同理可用A枚举后缀,用什么枚举,就是对应的P的系数

qian(S,l)表示字符串S的前l位,hou(S,L)表示字符串S的后l位,那么,第一个方程式右边可以写成

i=1m[qian(A,i)==hou(A,i)]PA×12mi+i=1m[qian(A,i)==hou(B,i)]PB×12mi

imSASA,SAi=m

有了这个思路,就能扩展了

对于xi,我们就用所有猜的串枚举后缀,根据匹配情况决定系数,匹配的就是qian(xi,a)

所以第i个方程长

PS×12m=i=1nj=1nlen=1m[qian(xi,len)==hou(xj,len)]xi×12mlen

再有一个

i=1nxi=1

就能解决了

n+1个方程,n+1个未知数(x1xn,PS

int main()
{
	scanf("%d%d",&n,&m);
	for(int i = 1;i <= n;i++) 
	{
		scanf("%s",s + 1);
		for(int j = 1;j <= m;j++)
		{
			qian[i][j] = qian[i][j - 1] * base + s[j] - 'A';//第i个串的前j位
		}
		ll o = 1;
		for(int j = 1;j <= m;j++)
		{
			hou[i][j] = hou[i][j - 1] + (s[m - j + 1] - 'A') * o;//第i个串的后j位
			o *= base;//这里因为是后缀所以后遍历到的对应的幂次反而大
		}
	}
	twopow[0] = 1; // 1 / (2^k)
	for(int i = 1;i <= m;i++) twopow[i] = twopow[i - 1] * 0.5;
	for(int i = 1;i <= n;i++)
	{
		for(int j = 1;j <= n;j++)
		{
			for(int l = 1;l <= m;l++)
			{
				if(qian[i][l] == hou[j][l]) a[i][j] += twopow[m - l];//处理系数
			}
		}
		a[i][n + 1] = -1.0 * twopow[m];//把P_S移项
		a[n + 1][i] = 1;//最后一个方程各未知数(除了P_S)系数均为1
	}
	a[n + 1][n + 2] = 1;
	n++;//n+1个方程
	gauss();
	for(int i = 2;i <= n;i++) printf("%.10lf\n",a[i][n + 1]);//这个范围是我输出了所有未知数的值后才得到的范围:[2,n+1]
	return 0;
}

太tm牛逼了直接给我neng死了

[ABC263E] Sugoroku 3

click

dpi表示in的期望

假设摇到了j[0,Ai]

不管j是多少,次数百分百会+1

又因为i+j都可以是目标点

那么可得

dpi=1+1Ai+1j=0Ai(E(i+jn))=1+1Ai+1j=0Aidpi+j=1+1Ai+1dpi+1Ai+1j=1Aidpi+j

化简

dpi=Ai+1Ai+1Aij=1Aidpi+j

后面的用后缀和来维护

记得疯狂取模,不然会得到6686不等

for(int i = n - 1;i >= 1;i--)
	{
		dp[i] = ((A[i] + 1) * inv(A[i]) % mod + (sum[i + 1] - sum[i + A[i] + 1]) * inv(A[i]) % mod) % mod;
		sum[i] = (sum[i + 1] + dp[i]) % mod;
	}
	printf("%lld",(dp[1] % mod + mod) % mod);

ABC323E Playlist

click

dp

dpi,j表示在第i时刻恰好放完第j首歌的概率

那么要让第一首歌覆盖x+0.5这个点,那么时长这个区间的后端点最小是x+1,最大是x+A1(此时前段正好是x,刚刚盖上)

所以答案是i=x+1x+A1dpi,1

然后剩下的前面的空区间就用其他歌去填满就行了

一开始想了半天,后来才发现是个完全背包

dpi,j=1ndpiAj,k,k[1,n]

后面的求和拿前缀和维护就行了

[cf261B]Maxim and Restaurant

click

和上一道题很像,也是用背包,这里是用背包求出方案数在乘以权值搞期望

定义dpi,j,k表示前i个人进去了j个,占据的空间为k

那么,对于第i个人,她可能在店内,也可能在店外,两种情况的方案和就是总方案

dpi,j,k=dpi1,j1,ka[i]+dpi1,j,k

但背包只是在拿大小说话,没有考虑排列,那么真正的方案数应当是dpi,j,k×j!×(nj1)!

为什么减一呢?这里就是另一个关键——对人进行排列的时候,堵门的人肯定不能动,不然后面的可能换到了门口就能进去了,和dp数组是不对应的

那么实际上,每一个dp都对应了一个堵门的人,这个人需要枚举,只有当枚举的这个人在该dp下能堵住门,即adu+k>p,该dp和对应的系数这个整体才是答案的一部分

code

posted @   why?123  阅读(22)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示