题解 P3600 【随机数生成器】
command_block
2019-01-01 19:33:13
~~蒟蒻又来肝期望题啦。~~
注意到这题$n,x$(由于本人习惯,下文均改为c)$,q$**同阶**。
非常罕见,一道期望题值域居然只有2000?
我们考虑**在值域上做手脚**。
```cpp
不确定的东西越多,题目就越复杂。
复杂的数学题可以利用枚举降低思考复杂度。
——某数学dalao
离散统计数学三兄弟:方案数,概率,期望
——本蒟蒻
```
喜闻乐见得,这道题有膜,所以不用单行掉精度,方案数,概率,期望可以随意转化。
这道题求的是一番操作后,答案的期望,很明显,答案一定是$[1,c]$内的一个整数(也就是只有c种可能),所以答案是离散期望变量。
离散变量期望公式:$E(x)=\sum{P(?)*W(?)}$,其中$P$是概率,$W$是权值
或者你可以看看[某度百科](https://baike.baidu.com/item/%E6%95%B0%E5%AD%A6%E6%9C%9F%E6%9C%9B/5362790?fr=aladdin)
比如说如果你抛瓶盖,瓶盖正面朝上的概率0.4,反面0.6;抛到正面得2分,反面得1分。
那么抛一次,得分期望为 $0.4*2+0.6*1=1.4$。
总的来说,概率乘权值(分数)的和就得到期望 。
- **设**答案有$p[x]$的概率等于$x$ 。
那么答案的期望$\large{E(ans)=\sum\limits_{1<=i<=x}{i*p[i]}}$
上面的的式子只有$p[]$是未知的,于是乎,我们**把期望问题转化为了概率问题**。
我们分别求出$p[1...c]$,再按照上面的式子算就可以了。
难点在$p$数组的求解上。
-------
这道题有一系列$min/max$操作,都是**基于不等式**的,我们设的状态很乏力(**等于**$x$),考虑转化一下。
概率是可以容斥的(废话),比如这道题里,我们就可以定义一个基于不等式的状态$g[x]$。
- 设$g[x]$表示答案有$g[x]$的概率小于等于x。
这样的话,$\large{p[x]=g[x]-g[x-1]}$。
$g[x]$相对好求一些(~~真香~~)。
我们把数列**大于$x$的数都转化为1,小于等于$x$的都转化为0**,很明显,每个位置有$(c-x)/c$的可能为$1,x/c$的可能为$0$。
由于取$min$操作,每个区间会得到一个0或者1,如果一个区间内有0则必然得到0,**如果这个区间全是1,则得到1**。
后面再对所有的区间取$max$,**如果区间答案有任意一个1,则$ans>x$**(~~坏了一锅粥?~~)。
所以$ans<=x$的概率等于有**没有一个区间内全是1**的概率。
好像这个概率也不好求,**转化成方案数**吧(zz气息)。
众所周知:事件的概率=**事件发生方案数/总方案数**(这要建立在所有情况概率(权重)相等的情况下)。
-------
- **设**$r[x]$表示答案有$r[x]$种方案小于等于$x$。
我们把数列大于$x$的数都转化为1,小于等于$x$的都转化为0,很明显,每个位置有$(c-x)$种方案为1,$x$种方案为0。
明显看到,总方案数为$c^n$(每个数字都有c种取值,共n个)
所以$\large{g[x]=r[x]/(c^n)}$。
所以$ans<=x$的方案数等于没有一个区间内全是1的方案数。
我们要保证没有一个区间内全是1,即每个区间内至少有1个0。
现在问题来了:我们有几种方法填写01串,使得每个区间内至少有1个0?
```cpp
不会就多设几个数。
——数学老师 forest·true·stop
```
- **设**$s[i]$为填了$i$个0,$(n-i)$个1,使得每个区间内至少有1个0的方案数。
一个1可以对应原值域中的$(c-x)$个数,一个0可以对应$x$个数。
则$\large{r[x]=\sum\limits_{0<=i<=n}{s[i]*(x^i*(c-i)^{(n-i)})}}$
~~对于$s$,上$O(2^n)$枚举01串求。~~(应该有这个部分分才对)
对于$s$,不就是个填写01串的方案数问题吗?终于可以**dp求解了**!
------
- 设$f[i][j]$为在前i位置内填了j个0(必填第i位),且已经覆盖了左端点小于等于i的所有区间的方案数。
转移:很明显$f[i][j]$要从$f[?][j-1]$转移过来。
具体是那些"?"呢?
如果第i位一个0可以把左端点$posl$满足$?<pos<=i$的所有区间都干掉,这个?就是满足条件的。
把?从$i-1$(两个0不能放在一起)向前枚举就可以了。
判断不合法:
如果有左端点posl满足$?<pos<=i$的区间,右端点端点$posr<i$,则?不在这个区间内,不合法。
因为上一个0没有干掉这个区间,这个0也没有,所以这个区间可以~~苟且偷生~~,所以布星。
那么,求出来?之后 ,得到$\large{f[i][j]=\sum\limits_{?<=j<i}f[j][j-1]}$
正确性满足了,接下来**上优化**。
-------
我们来捋一捋程序过程与复杂度($n,c,q$同阶)。
先$dp$出$f$数组,一共状态$O(n^2)$,乘上转移$O(n)$。 O(n^3)
$s[k]=\sum\limits_{1<=i<=n}{f[i][k]}\text{(i需要满足条件哦)}$ O(n^2)
$r[x]=\sum\limits_{0<=i<=n}{s[i]*(i^k*(x-i)^{(n-k)})}$ O(n^2*logn)(快速幂)
$g[x]=r[x]/(c^n)$ O(n)
$p[x]=g[x]-g[x-1]$ O(n)
$E(ans)=\sum\limits_{1<=i<=x}{i*p[i]}$ O(n)
空间无疑$O(n^2)$时间复杂度$O(n^3)$
**瓶颈在dp**,想办法加速转移。
回顾:如果有左端点posl满足$?<pos<=i$的区间,右端点端点$posr<i$,则?不在这个区间内,不合法。
很明显具有单调性嘛。
右端点做一个ST表,查处最小值,如果最小值$<i$,则$?$不合法(时间复杂度$O(1)$)。
二分+ST表就可以解决。
$?$的求解降到$O(logn)$剩下的加上前缀和就可以啦。
复杂度优化为$O(n^2logn)$,解决本题。
~~代码有点恶心人~~
------------
其实不用二分优化,大力枚举r就可以了。
由于数据随机,r期望只用枚举$log(n)$次
上暴力版的代码吧,反正也AC了。
~~正常版的太丑,不放了。~~
**Code: **
```cpp
#include<algorithm>
#include<cstdio>
#define mod 666623333
using namespace std;
int n,c,q,h[2050];
long long f[2050][2050]
,s[2050],r[2050],g[2050],p[2050],ans;
struct Line
{int l,r;}
b[2050];
long long pow(long long a,long long t)
{
long long ans=1,buff=a;
while(t){
if (t&1) ans=(ans*buff)%mod;
buff=(buff*buff)%mod;
t>>=1;
}return ans;
}
long long inv(long long num)
{return pow(num,mod-2);}
int main()
{
scanf("%d%d%d",&n,&c,&q);
for (int i=1;i<=n;i++)h[i]=1<<30;
for (int i=1;i<=q;i++){
scanf("%d%d",&b[i].l,&b[i].r);
h[b[i].l]=min(h[b[i].l],b[i].r);//这句话是关键
}f[0][0]=1;
for (int j=1;j<=n;j++){
for (int i=j;i<=n;i++){
int r=i-1;
while(h[r]>=i&&r){
f[i][j]=(f[i][j]+f[r][j-1])%mod;
r--;//一开始没有膜还得了80
}f[i][j]=(f[i][j]+f[r][j-1])%mod;
//追求理论复杂度的话,可以把转移改成ST表+二分+前缀和优化(汗)
}
}for (int k=0;k<=n;k++)
for (int i=n;i>-1;i--){
s[k]=(s[k]+f[i][k])%mod;
if (h[i]<(1<<25))break;
//如果这里放最后一个0满足不了最后一个区间,在前面放0都不合法。
}
for (int x=1;x<=c;x++)
for (int i=1;i<=n;i++)
r[x]=(r[x]+s[i]*pow(x,i)%mod*pow(c-x,n-i))%mod;
for (int x=1;x<=c;x++)g[x]=r[x]*inv(pow(c,n))%mod;
for (int x=1;x<=c;x++)p[x]=(g[x]-g[x-1]+mod)%mod;
for (int x=1;x<=c;x++)ans=(ans+x*p[x])%mod;
//跟上面的式子一样的。
printf("%lld",ans);
return 0;
}
```
[$AC$记录](https://www.luogu.org/recordnew/show/15145944)