hdu 4576(简单概率dp | 矩阵优化)

艰难的一道题,体现出菜菜的我。。。

首先,先吐槽下。 这题到底出题人是怎么想的,用普通概率dp水过??? 那为什么我概率dp写的稍微烂点就一直tle?  感觉很不公平。大家算法都一致,因为我程序没有那么简练就过不了。 太坑了。。。

当然,说到底还是实力的问题,谁叫你不会一些常数级别的优化。 谁叫你写的时候不写的好一点。

比赛的时候在速度秒掉了最后两题后,卡这道题卡了4个多小时,也没有心情去看其他的题目了。 期间想了各种优化的方法。 最后因为一个小错误wa了N次后没有过而遗憾终身。。。

1. 直接 概率dp ,时间可能会很长,因为循环的次数达到了10^8次方,所以循环最内部的运算必须不能过多。 如果剩去%(这个比较费时的,话说至少是+,-的5、6倍吧)还有输入用自己定义的函数(就是不用scanf,用getchar()一个一个的读入)可以将时间缩到<2000ms

附一份普通的3000+ms的

#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <iostream>
using namespace std; double dp[][]; int main()
{
//freopen("//home//chen//Desktop//ACM//in.text","r",stdin);
int n,m,l,r;
while(scanf("%d%d%d%d",&n,&m,&l,&r)&&(n+m+l+r))
{
int a=,b=;
memset(dp,,sizeof(dp));
dp[a][]=;
for(int i=;i<m;i++)
{
int tmp;
scanf("%d",&tmp);
tmp%=n;
for(int j=;j<=n;j++)
{
dp[b][j]=dp[a][(j+tmp-)%n+]*0.5+dp[a][(j-tmp+n-)%n+]*0.5;
}
swap(a,b);
}
double ans=;
for(int i=l;i<=r;i++)
ans+=dp[a][i];
printf("%.4lf\n",ans);
}
return ;
}

2. 可以用矩阵优化。

首先可以知道的是这个m次运算是满足结合律的。 于是快速幂的思想就可以运用了。

统计出m次运算中 移k(1<= k <=200)位的个数。 然后这些移相同位的运算都是相同的,可以用快速幂使复杂度变成log,也就是将m次运算变成 约等于logm

构造一个矩阵,发现如果直接用矩阵乘法的话,一次相乘复杂度就达到了n*n*n也就是8*10^6 。

但是因为这题的特殊性,构造出来的矩阵对于每一列仅有的两个元素都恰好是等差错开排列的。

a b 0      a b 0      a*a   2ab   b*b

0 a b  *  0 a b  =  b*b   a*a   2ab

b 0 a      b 0 a      2ab  b*b   a*a

这里a=b=0.5 ,(还有可能a,b在同一个位置,这时只有一个a=1). 而且值得注意的时候一般有规律的矩阵乘出来的结果一般也是有规律的。

这样就可以只求出第一行的结果,然后剩余的用第一行的结果推就行了。这样复杂度就为n*n

总的复杂度是n*n*k*logm。 理论上可以接受,而且应该没有这么大,因为不可能每次进行快速幂的复杂度都达到logm。

以上有一部分引用,代码也在其中 http://www.cnblogs.com/kiwi-bird/archive/2013/08/10/3250696.html

时间大概1700+ms

3. 我自己想出来的一种类似于矩阵快速幂的方法,至少思想是这样的。 时间1400+ms

算法原理,思想都类似于快速幂,当时感觉也不是特别好就不详细解释了。

#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <iostream>
using namespace std; int n,m,l,r;
int save[];
double g[];
double tmp[];
double sum[]; //理论上满足结合律 int main()
{
//freopen("//home//chen//Desktop//ACM//in.text","r",stdin);
while(~scanf("%d%d%d%d",&n,&m,&l,&r)&&(n+m+l+r))
{
memset(save,,sizeof(save));
for(int i=;i<m;i++)
{
int tmp1;
scanf("%d",&tmp1);
tmp1=tmp1%n;//n就是走了0步
save[tmp1]++;
} memset(g,,sizeof(g));
g[]=;
for(int ii=;ii<n;ii++) //有save[ii] 个不同的
{
if(save[ii]==) continue;
//////////////////
int cnt;
while(save[ii])
{
cnt=;
memset(tmp,,sizeof(tmp));
tmp[ii+]=0.5;//因为ii+1可能会和n+1-ii相等所以下一步用+=是必须的
tmp[n+-ii]+=0.5; //wa 了N次在这一步. 思维还是想不到很全面
while(*cnt <= save[ii])
{
memset(sum,,sizeof(sum));
for(int i=;i<=n;i++) //从第一个位置开始
{
for(int j=;j<=n;j++)
{
int ttmp=i+j-;
if(ttmp > n) ttmp -= n;
sum[ttmp] += tmp[i]*tmp[j];
}
}
for(int i=;i<=n;i++)
tmp[i]=sum[i];
cnt=cnt+cnt;
}
memset(sum,,sizeof(sum)); for(int i=;i<=n;i++)
{
for(int j=;j<=n;j++)
{
int ttmp=i+j-;
if(ttmp>n) ttmp -= n;
sum[ttmp] += g[i]*tmp[j];//每个点对其他的贡献
}
}
for(int i=;i<=n;i++)
g[i]=sum[i]; save[ii]-=cnt;
}
} double ans=;
for(int i=l;i<=r;i++)
ans += g[i];
printf("%.4lf\n",ans);
}
return ;
}
上一篇:2016huasacm暑假集训训练三 D - Invitation Cards


下一篇:Aeroplane chess(简单概率dp)