[CSP校内集训]kat(期望DP)
题意
一本有n道题的练习册,katarina大佬每天都会做k道题。
第一天做第1k题,第二天做第2k+1题……第n-k+1天做第n−k+1~n道题。
每道题有它的难度值,假设今天katarina大佬做的题目中最大难度为t,那么今天katarina大佬的劳累度就是\(w_t\),做完这本书的劳累值就是每天的劳累值之和。
题目的难度在1~m随机,求做完整本书的期望劳累值
\(n,m \leq 500 , mod = 10^9+7\)
思路
由于前面两道题都有点迷所以还以为这道题也是道神题
性质:求每种排列的贡献和 等价于 一个滑动窗口的贡献\(\times\) \((n-k+1)\)
(性感)证明
根据题意有:$$ans = \sum_{i=1}{mn}{ (\frac{1}{m^n} \times (a_1^i + a_2^i + ....... + a_{n-k+1}^i)) }$$其中\(a_j^i\)表示在第\(i\)种排列下第\(j\)个滑动窗口的贡献
显然这个东西可以用乘法分配律按照\(j\)分开:$$ans = \sum_{j=1}{n-k+1}{(\frac{1}{mn} \times (a_j^1 + a_j^2 + ....... + a_j{mn}))}$$
对于一个滑动窗口,在它范围外的其他格子不管怎么变都不会影响它的贡献,所以这样的\(a\)是相同的,每种贡献有\(m^{n-k}\)个
把相同贡献的变成一个,最后变成:$$ans = \sum_{j=1}^{n-k+1}{ (\frac{1}{m^k} \times (a_j^1 + a_j^2 + ........ + a_j{m{n-k}}))) }$$
发现此时答案为每个滑动窗口单独的贡献加起来,而每个滑动窗口的贡献又相同,所以就等于单个贡献\(\times\) \((n-k+1)\)
对单个滑窗期望DP即可,这个复杂度可以是\(O(mlogn)\)或者\(O(nm)\),比较基础就不写了(不能理解题解\(O(n^2m)\)的睿智做法
本题难点不在期望DP而在简化问题(当然像题解一样直接莽就另当别论了),其实\(m\)可以开到\(10^5\)
Code
#include<bits/stdc++.h>
#define N 505
#define Min(x,y) ((x)<(y)?(x):(y))
#define Max(x,y) ((x)>(y)?(x):(y))
using namespace std;
typedef long long ll;
const ll mod = 1000000007;
int n,m,k;
ll a[N],f[N][N],sum[N][N];
template <class T>
void read(T &x)
{
char c; int sign=1;
while((c=getchar())>'9'||c<'0') if(c=='-') sign=-1; x=c-48;
while((c=getchar())>='0'&&c<='9') x=(x<<1)+(x<<3)+c-48; x*=sign;
}
ll quickpow(ll a,ll b)
{
ll ret=1;
while(b)
{
if(b&1) ret=ret*a%mod;
a=a*a%mod;
b>>=1;
}
return ret;
}
int main()
{
freopen("kat.in","r",stdin);
freopen("kat.out","w",stdout);
read(n);read(m);read(k);
for(int i=1;i<=m;++i) read(a[i]);
for(int i=1;i<=m;++i) f[1][i]=quickpow(m,mod-2),sum[1][i]=(sum[1][i-1]+f[1][i])%mod;
for(int i=2;i<=k;++i)
{
for(int j=1;j<=m;++j)
{
f[i][j]=(sum[i-1][j-1]+f[i-1][j]*j%mod)*quickpow(m,mod-2)%mod;
sum[i][j]=(sum[i][j-1]+f[i][j])%mod;
}
}
ll ans=0;
for(int i=1;i<=m;++i) ans=(ans + f[k][i]*a[i]%mod)%mod;
cout<<(ans*(n-k+1)%mod+mod)%mod<<endl;
return 0;
}