【bzoj2510】【期望dp】【循环矩阵快速幂】弱题

【描述】
有M个球,一开始每个球均有一个初始标号,标号范围为1~N且为整数,标号为i的球有ai个,并保证Σai = M。

每次操作等概率取出一个球(即取出每个球的概率均为1/M),若这个球标号为k(k < N),则将它重新标号为k + 1;若这个球标号为N,则将其重标号为1。(取出球后并不将其丢弃)

现在你需要求出,经过K次这样的操作后,每个标号的球的期望个数。

【输入】
第1行包含三个正整数N,M,K,表示了标号与球的个数以及操作次数。

第2行包含N个非负整数ai,表示初始标号为i的球有ai个

【输出】
应包含N行,第i行为标号为i的球的期望个数,四舍五入保留3位小数。

【样例输入】
2 3 2
3 0

【样例输出】
1.667
1.333
N ≤ 1000 , M ≤ 100 , 000 , 000 , K ≤ 2 , 147 , 483 , 647 N ≤ 1000, M ≤ 100,000,000, K ≤ 2,147,483,647 N1000,M100,000,000,K2,147,483,647

【思路】

转移十分显然,定义 f [ i ] [ k ] f[i][k] f[i][k]表示k次操作之后编号为i的球的期望个数:
f [ i ] [ k ] = ( 1 / m ) ∗ f [ i − 1 ] [ k − 1 ] + ( ( m − 1 ) / m ) ∗ f [ i ] [ k − 1 ] f[i][k]=(1/m)*f[i-1][k-1]+((m-1)/m)*f[i][k-1] f[i][k]=(1/m)f[i1][k1]+((m1)/m)f[i][k1]
于是我们就可以矩阵快速幂转移啦。好像有什么不对…N<=1000。此时如果你有相当玄学的卡常技巧,你就可以去写代码了,相信你能把常数卡成1/1000 。我们发现,转移矩阵是循环相同的。

循环矩阵例子:
[ 1 2 3 3 1 2 2 3 1 ] \begin{bmatrix} 1 &amp; 2&amp;3\\ 3&amp; 1&amp;2 \\2&amp;3&amp;1 \end{bmatrix} 132213321
循环矩阵指的是上面这种矩阵。维护这样的矩阵很简单。我们发现,这样的矩阵自乘若干次以后仍然是循环矩阵,这意味着我们只需要维护一行即可。此时矩阵乘法类似于多项式乘法: c [ ( i + j ) m o d n ] + = a [ i ] ∗ b [ j ] c[(i+j)modn]+=a[i]*b[j] c[(i+j)modn]+=a[i]b[j]。很好记对吧?所以我们就可以在 O ( n 2 l o g n ) O(n^2logn) O(n2logn)内解决问题,甚至可以使用fft优化。(不过由于fft自带大常数,所以效率差不多)。
代码:

#include<bits/stdc++.h>
#define re register
using namespace std;
const int N=1e3+5;
inline int red(){
    int data=0;bool w=0; char ch=0;
    ch=getchar();
    while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
    if(ch=='-') w=1,ch=getchar();
    while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
    return w?-data:data;
}
int n,m,c,k;
struct mat{
	double a[N];
	friend inline mat operator*(const mat&a,const mat&b){
		mat c;memset(c.a,0,sizeof(c.a));
		for(int re i=0;i<n;i++)
			for(int re j=0;j<n;j++)
				c.a[(i+j)%n]+=a.a[i]*b.a[j];
		return c;
	}
}ans,A;
void ksm(){while(k){if(k&1)ans=ans*A;A=A*A,k>>=1;}}
int main(){
	n=red();m=red();k=red();
	for(int re i=0;i^n;++i)ans.a[i]=red();
	A.a[0]=1.0*(m-1)/m,A.a[1]=1.0/m;ksm();
	for(int re i=0;i^n;++i)printf("%.3f\n",ans.a[i]);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值