流星雨(概率dp)

链接:https://ac.nowcoder.com/acm/contest/368/C
来源:牛客网
 

题目描述

现在一共有n天,第i天如果有流星雨的话,会有wiwi颗流星雨。

第i天有流星雨的概率是pipi。

如果第一天有流星雨了,那么第二天有流星雨的可能性是p2+Pp2+P,否则是p2p2。相应的,如果第i−1 (i≥2)i−1 (i≥2)天有流星雨,第i天有流星雨的可能性是pi+Ppi+P,否则是pipi。

求n天后,流星雨颗数的期望。

输入描述:

 

第一行三个整数,n,a,b,其中n为天数,P=abP=ab

第二行n个整数wiwi。

接下来n行,每行两个整数,x,y,第i+2行表示第i天有流星雨的概率pi=xypi=xy。

1≤n≤105, 1≤a,b,x,y,wi≤109, pi+P≤1.01≤n≤105, 1≤a,b,x,y,wi≤109, pi+P≤1.0

输出描述:

 

一行一个整数,为答案对109+7109+7 取模的结果。

即设答案化为最简分式后的形式为abab,其中a和b互质。输出整数 x 使得bx≡a(mod 109+7)bx≡a(mod 109+7)且0≤x<109+70≤x<109+7。可以证明这样的整数x是唯一的。

示例1

输入

复制

2 1 3
1 1 
1 2
1 2

输出

复制

166666669

说明

 

第一天有流星雨第二天也有流星雨的概率是12×(12+13)12×(12+13),然后乘以流星雨的颗数2

第一天有流星雨第二天没有流星雨的概率是12×1612×16,乘以颗数1

第一天没有,第二天有的概率12×1212×12,乘以颗数1

第一天没有,第二天也没有的概率12×1212×12,乘以颗数0。

所以流星雨颗数的期望是7676

示例2

输入

复制

3 1 5
1 1 2
1 2
1 4
2 3

输出

复制

763333341

这题比较小白的我用dfs去写,准备超时,于是:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
typedef long long ll;
ll mod=1e9+7; 
ll gcd(ll a,ll b) { return b?gcd(b,a%b):a;}
ll powmod(ll a,ll b) {ll res=1;a%=mod; assert(b>=0); 
for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
int n;
ll a,b;
ll ansfz,ansfm;
ll pfz[maxn],pfm[maxn],num[maxn];
ll dp[maxn][2];
struct node
{
	ll fz,fm;
}dp[maxn][2];
void cal(ll &x,ll &y,ll a,ll b,ll a1,ll b1)//相加 
{
	if(a==0)
	{
		x=a1,y=b1;
		return ;
	}
	ll fz=(a*b1%mod+a1*b%mod)%mod;
	ll fm=b*b1%mod;
	ll d=gcd(fz,fm);
	if(d==0)
	{
		x=fz;
		y=fm;
		return ;
	}
	x=fz/d;
	y=fm/d;
}
void cal1(ll &x,ll &y,ll a,ll b,ll a1,ll b1)//相乘 
{
	ll fz=a*a1%mod;
	ll fm=b*b1%mod;
	ll d=gcd(fz,fm);
	if(d==0)
	{
		x=fz;
		y=fm;
		return ;
	}
	x=fz/d;
	y=fm/d;
}
void dfs(int i,int pre,ll fz,ll fm,ll sum)
{
//	printf("i:%d\n",i);
	if(i==n+1)
	{
		ll f1,f2;
		cal1(f1,f2,sum,1,fz,fm);
		cal(ansfz,ansfm,ansfz,ansfm,f1,f2);//相加 
		return ;
	}
	if(pre==0)
	{	
		ll f1,f2;
		cal1(f1,f2,fz,fm,pfz[i],pfm[i]);
		dfs(i+1,1,f1,f2,sum+num[i]);
		cal1(f1,f2,fz,fm,pfm[i]-pfz[i],pfm[i]);
		dfs(i+1,0,f1,f2,sum);
	}
	else
	{
		ll f1,f2;
		ll d1,d2;
		cal(d1,d2,a,b,pfz[i],pfm[i]);
		cal1(f1,f2,fz,fm,d1,d2);
		dfs(i+1,1,f1,f2,sum+num[i]);
		cal1(f1,f2,fz,fm,d2-d1,d2);
		dfs(i+1,0,f1,f2,sum);
	}
}
int main()
{
	cin>>n>>a>>b;
	for(int i=1;i<=n;i++)  scanf("%lld",&num[i]);
	for(int i=1;i<=n;i++) 
		scanf("%lld%lld",&pfz[i],&pfm[i]);
	dfs(1,0,1,1,0);
	cout<<ansfz*powmod(ansfm,mod-2)%mod<<endl;
//	cout<<(ansfz+mod)/(ansfm%mod)<<endl;
}

很显然是超时的,看了其他人的题解后学到了一些新知识:

1、概率可以用逆元去解决处理,无需自己写一个函数处理分数的相加相减,p1是下雨,逆元中(1-p1+mod)%mod也是代表不下雨;

2、该题期望有两种算法:总概率p*总数量num;也可以当前i下雨的概率pi*num[i],然后求和。

有了以上知识就可以轻松写出代码了,由于自己菜,不懂这些,当时就是不会写,现在懂了~

​
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
typedef long long ll;
const ll mod=1e9+7;
ll powmod(ll a,ll b) {ll res=1;a%=mod; assert(b>=0); 
for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
ll num[maxn];
ll d[maxn][2];
int main()
{
	int n;
	ll a,b;
	cin>>n>>a>>b;
	ll p=a*powmod(b,mod-2)%mod;
	for(int i=1;i<=n;i++) scanf("%lld",&num[i]);
	ll ans=0;
	for(int i=1;i<=n;i++)
	{
		scanf("%lld%lld",&a,&b);
		if(i==1)
		{
			ll p1=a*powmod(b,mod-2)%mod;
			d[1][1]=p1;
			d[1][0]=(1-p1+mod)%mod;
			ans=p1*num[1]%mod;
			continue;
		}
		ll p1=a*powmod(b,mod-2)%mod;//下 
		ll p0=(1-p1+mod)%mod;
		ll p2=(p1+p)%mod;//下 
		ll p3=(1-p2+mod)%mod;
		d[i][0]=(d[i-1][0]*p0%mod+d[i-1][1]*p3%mod)%mod;
		d[i][1]=(d[i-1][0]*p1%mod)+(d[i-1][1]*p2%mod)%mod;
		ans=(ans+d[i][1]*num[i]%mod)%mod;
	}
	cout<<ans<<endl;
}

​

猜你喜欢

转载自blog.csdn.net/qq_41286356/article/details/87265819