1. 程式人生 > >【BZOJ2510】弱題 期望DP+循環矩陣乘法

【BZOJ2510】弱題 期望DP+循環矩陣乘法

mem ret sam include std bsp 都是 size 個數

【BZOJ2510】弱題

Description

M個球,一開始每個球均有一個初始標號,標號範圍為1~N且為整數,標號為i的球有ai個,並保證Σai = M。 每次操作等概率取出一個球(即取出每個球的概率均為1/M),若這個球標號為kk < N),則將它重新標號為k + 1;若這個球標號為N,則將其重標號為1。(取出球後並不將其丟棄) 現在你需要求出,經過K次這樣的操作後,每個標號的球的期望個數。

Input

第1行包含三個正整數NMK,表示了標號與球的個數以及操作次數。 第2行包含N非負整數ai,表示初始標號為i的球有ai個。

Output

應包含N行,第i行為標號為i的球的期望個數,四舍五入保留3位小數。

Sample Input

2 3 2
3 0

Sample Output

1.667
1.333

HINT

【樣例說明】
第1次操作後,由於標號為2球個數為0,所以必然是一個標號為1的球變為標號為2的球。所以有2個標號為1的球,有1個標號為2的球。
第2次操作後,有1/3的概率標號為2的球變為標號為1的球(此時標號為1的球有3個),有2/3的概率標號為1的球變為標號為2的球(此時標號為1的球有1個),所以標號為1的球的期望個數為1/3*3+2/3*1 = 5/3。同理可求出標號為2的球期望個數為4/3。
【數據規模與約定】

對於10%的數據,N ≤ 5, M ≤ 5, K ≤ 10;
對於20%的數據,N ≤ 20, M ≤ 50, K ≤ 20;
對於30%的數據,N ≤ 100, M ≤ 100, K ≤ 100;
對於40%的數據,M ≤ 1000, K ≤ 1000;
對於100%的數據,N ≤ 1000, M ≤ 100,000,000, K ≤ 2,147,483,647。

題解:一開始too naive,以為同樣用一個期望DP的黑科技就能過(k=min(k,5000))~

發現正解又是矩陣乘法,但是矩乘不是n^3的嗎?本題有特殊性質。

我們的DP方程長這樣:f[i][j]=f[i的上一個][j-1]/m+f[i][j-1]*(m-1)/m

所以我們的轉移矩陣的每一行都是循環相同的,將轉移矩陣自乘若幹次後,每一行仍然是循環相同的,所以我們的矩陣實際上只需要維護一行,那麽轉移一次的代價自然就是O(n^2)的。

那麽具體實現呢?其實非常簡單,直接c[i+j]+=a[i]*b[j]。

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int n,m,k;
struct M
{
	double v[1010];
	M (){memset(v,0,sizeof(v));}
	double& operator [] (int x)	{return v[x];}
	M operator * (M a)
	{
		M ret;
		for(int i=0;i<n;i++)	for(int j=0;j<n;j++)	ret[(i+j)%n]+=v[i]*a[j];
		return ret;
	}
};
M ans,x;
void pm(int y)
{
	while(y)
	{
		if(y&1)	ans=ans*x;
		x=x*x,y>>=1;
	}
}
int main()
{
	scanf("%d%d%d",&n,&m,&k);
	int i;
	for(i=0;i<n;i++)	scanf("%lf",&ans[i]);
	x[0]=(double)(m-1)/m,x[1]=(double)1/m;
	pm(k);
	for(i=0;i<n;i++)	printf("%.3lf\n",ans[i]);
	return 0;
}

【BZOJ2510】弱題 期望DP+循環矩陣乘法