链接:https://ac.nowcoder.com/acm/contest/11255/B
这篇题解可能会有些许错误,欢迎大佬们指正Orz
概率生成函数
首先,我们定义概率生成函数(为啥这么定义?菜了,不大清楚,当一个做题方向吧)
我们现在需要的出这个函数。
由题意可知:我们抽到的数字的序列,除去最后一个数之外,它肯定是一个不下降的序列,像是1,1,2,3,3,7这样的。这时,我们可以枚举每个数的出现次数,来得到(
指的是数字i被抽到的概率)
具体的,假如1,1,2,3,3,7这样的序列,对应的就是。我们某个数出现的次数的生成函数
,则我们可以知道
。
根据等比数列求和,
因此,
记住这个函数,我们后面会用到它。
PS.枚举了1,1,3,3,7这种序列后游戏并没有结束,因此它是P(len>i)的概率。
回到题目,我们要求的是的期望,那么,用上面的东西表示其实是:
这里有一些细节可能需要学过生成函数才能比较好理解,这里不再展开。
ps.官方题解这里第一步有些许不同,但是推到结果相同,不知道是我理解有错还是出题人笔误?欢迎大佬们指出。
upd.这里有大佬说推导会漏掉
这一项,但目前还没想出问题所在Orz。
upd 2021.7.21. 修改了第4行处一处错误,感谢各位大佬的指正Orz。
注意:以后推累加的式子最好都把前几项写出来看下。
之后就是怎么求这个式子的问题了。
首先是怎么求,这个好解决,
。
之后,根据,x=1带入也可以得到f(1)。
而对于,我们这样考虑:
对于形如函数,其导数为
因此,对于,g(x)形如h(x)
其导数为
因此,
这样就求和,后面求和直接计算出来,乘上f(1)即可。
#include <bits/stdc++.h>
#include <iostream>
#include <ctime>
#include <cstdio>
#include <cctype>
#define inf 0x3f3f3f3f
#define IOS \
ios_base::sync_with_stdio(0); \
cin.tie(0);
#define rep(i, a, n) for (int i = a; i <= n; ++i)
#define per(i, a, n) for (int i = n; i >= a; --i)
//#define ONLINE_JUDGE
using namespace std;
typedef long long ll;
const int mod = 998244353;
namespace FastIO
{
char buf[1 << 21], buf2[1 << 21], a[20], *p1 = buf, *p2 = buf, hh = '\n';
int p, p3 = -1;
void read() {}
void print() {}
inline int getc()
{
return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++;
}
inline void flush()
{
fwrite(buf2, 1, p3 + 1, stdout), p3 = -1;
}
template <typename T, typename... T2>
inline void read(T &x, T2 &...oth)
{
int f = 0;
x = 0;
char ch = getc();
while (!isdigit(ch))
{
if (ch == '-')
f = 1;
ch = getc();
}
while (isdigit(ch))
{
x = x * 10 + ch - 48;
ch = getc();
}
x = f ? -x : x;
read(oth...);
}
template <typename T, typename... T2>
inline void print(T x, T2... oth)
{
if (p3 > 1 << 20)
flush();
if (x < 0)
buf2[++p3] = 45, x = -x;
do
{
a[++p] = x % 10 + 48;
} while (x /= 10);
do
{
buf2[++p3] = a[p];
} while (--p);
print(oth...);
}
} // namespace FastIO
#define read FastIO::read
#define write FastIO::print
int gcd(int a, int b)
{
return b == 0 ? a : gcd(b, a % b);
}
int lcm(int a, int b) { return a / gcd(a, b) * b; };
ll ksm(ll a, ll n)
{
ll ans = 1;
while (n)
{
if (n & 1)
ans = (ans * a) % mod;
a = a * a % mod;
n >>= 1;
}
return ans % mod;
}
//==============================================================
const int maxn=105;
#define MOD(x) (((x)%mod+mod)%mod)
ll n,p[maxn];
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
//clock_t c1 = clock();
//===========================================================
read(n);
int sum=0;
rep(i,1,n)read(p[i]),sum=(sum+p[i]);
for(int i=1;i<=n;++i){
p[i]=MOD(p[i]*ksm(sum,mod-2));
}
ll fval=1;
for(int i=1;i<=n;++i){
fval*=ksm(1-p[i],mod-2);
fval=MOD(fval);
}
ll gval=0;
for(int i=1;i<=n;++i){
gval+=1ll*p[i]*ksm(1-p[i],mod-2);
gval=MOD(gval);
}
ll f_val=gval*fval;
cout<<MOD(f_val*2%mod+fval)<<endl;
//===========================================================
FastIO::flush();
//std::cerr << "Time:" << clock() - c1 << "ms" << std::endl;
return 0;
}
期望dp
看到有大佬用的期望dp。其实比赛的时候也想过,但是最后十分钟发现题意假了,怕了,没想出来。
其实期望dp的转移是比较套路的,一般难点在状态设计上。但是这题的状态设计其实又比较明显:
对于的期望,我们一般还是先考虑求
的期望,再根据期望的线性性解决。
由于这题的步骤可以有无限步,因此不可能选择步数作为状态。显然,我们选择的会是目前的最大值。
dp[i]表示非递减部分现在最大值为i时,还需要生成的数的个数的期望,则:
dp[i]有两个来源:
1、下一步选到了一个数j>=i,则还需要继续选择,这一步的概率为,
2、下一步选到了一个数j<i,则下一步就结束,这一步的概率也为
因此,可以列出dp转移方程:
化简下就是
从n->1推即可。
对于:
由
得:
同理化简得:
同样推即可。
答案为:
或者上面的
因为我们算(i>0)实际是已经选取了一个数后再进行的。
mod=998244353
dp=[i for i in range(105)]
dp2=[i for i in range(105)]
p=[i for i in range(105)]
n=int(input())
w=list(map(int,input().split()))
sum=0
for x in w:
sum+=x
Inv=pow(sum,mod-2,mod)
for i in range(n):
p[i]=w[i]*Inv%mod
for i in reversed(range(n)):
pre=0
suf=0
for j in range(i):
pre+=p[j]
pre%=mod
for j in range(i+1,n):
suf+=p[j]*(dp[j]+1)%mod
suf%=mod
inv=pow((1-p[i])%mod,mod-2,mod)
dp[i]=(pre+suf+p[i])*inv%mod
for i in reversed(range(n)):
up=0
for j in range(i+1,n):
up+=p[j]*(dp2[j]+2*dp[j]+1)%mod
up%=mod
for j in range(i):
up+=p[j]
up%=mod
up+=2*p[i]*dp[i]+p[i]
up%=mod
down=pow((1-p[i])%mod,mod-2,mod)
dp2[i]=up*down%mod
print(dp2[0])

京公网安备 11010502036488号