[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)

思路

由于前面两道题都有点迷所以还以为这道题也是道神题

性质:求每种排列的贡献和 等价于 一个滑动窗口的贡献( imes) ((n-k+1))

性感)证明

根据题意有:$$ans = sum_{i=1}{mn}{ (frac{1}{m^n} imes (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} imes (a_j^1 + a_j^2 + ....... + a_j{mn}))}$$

对于一个滑动窗口,在它范围外的其他格子不管怎么变都不会影响它的贡献,所以这样的(a)是相同的,每种贡献有(m^{n-k})

把相同贡献的变成一个,最后变成:$$ans = sum_{j=1}^{n-k+1}{ (frac{1}{m^k} imes (a_j^1 + a_j^2 + ........ + a_j{m{n-k}}))) }$$

发现此时答案为每个滑动窗口单独的贡献加起来,而每个滑动窗口的贡献又相同,所以就等于单个贡献( imes) ((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;
}
原文地址:https://www.cnblogs.com/Chtholly/p/11762707.html