Comet OJ - Contest #0 B 旅途(level 3)(概率dp)

题目链接

题意:

有一个n个点的环,编号为1...n

第一天你在节点1,每一天你会在这个点游玩

然后下一天,你有3种选择

1.p%的概率顺时针走一步,到下一个城市游玩

2.q%的概率逆时针走一步,到上一个城市游玩

3.(100-p-q)%的概率待在原地再玩一天

设f[i]=在第m天后,你游玩了i个城市的概率

请你计算100^{m-1}\sum _{i=1}^{n}i^{k}f(i)对1e9+7取模的结果

解析:

这道题看到的时候,就感觉用概率DP来做。
但是dp这个东西要看脑子,要善于把握影响答案的直接因素。
所以往往是找这个直接因素是最难的,也就是dp[]每一维代表什么

我一开始想的时候想用dp[i][j][k],i:天数,j:当前位置,k:走过的城市的数量
但是这样我发现还是不行,一个dp[i][j][k]会表示两种不同的状态,以致于没有办法写转移式
譬如dp[2][0][2],这个表示的有两种状态—— 1.0->1->0  2.0->n-1->0
我把这两种状态写在一起,对下一状态的转移会有很大的影响
我们转移dp[2][0][2]这个状态,当向顺时针走一步的时候,上面两种情况会进入不同的状态
1会进入dp[3][1][2]  ,但2会进入dp[3][1][3]。但是在算的时候我已经把这两个状态的概率加在了一起
所以导致后面根本没有办法转移...
后来想了很多办法,但都把其中的一维开成了当前位置j

看了题解之后,发现位置并不是影响答案的直接因素..
当前位置是通过影响当前位置到顺时针最远到达的点的距离和当前位置到逆时针最远到达的距离来影响答案的。
即人当前在逆时针最远到的点到顺时针最远到的点的这段距离中的相对位置在直接影响答案。
下面看官方题解理解一下

对于这道题之间复杂度,因为你在求dp的时候每一维满足

x,y>=0

x+y+1<=n

x+y+1<=i

所以时间复杂度好像不会特别高,看了讲解好像时间复杂度是这个东西

这道题其实你只要取模取的少,就可以跑的快,我看那些334ms过的,都是

枚举dp[i][j][k]看这个状态可以从哪些状态转移来,那么对于每一个dp[i][j][k] 只需要取一次模就可以了

不过那个dp我没看懂,.,,,,

所以我换了一个稍微加了点内存优化,看起来好看点的写法,我自己最下面写的那个太丑了...我把人在区间端点和在区间内的情况分开来考虑。后来看了别人的代码,其实只需要用max,min来限定范围就可以了

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
 
typedef long long ll;
const int MAX = 501;
const int MOD = 1e9+7;
 
int dp[2][MAX][MAX];
int po[MAX][MAX];
 
void init()
{
    for(int i=1;i<MAX;i++){
        po[i][0]=1;
        for(int j=1;j<MAX;j++){
            po[i][j]=1ll*po[i][j-1]*i%MOD;
        }
    }
}
 
int main()
{
    int t;
    scanf("%d",&t);
    init();
    while(t--){
        int n,m,k,p,q;
        scanf("%d%d%d%d%d",&n,&m,&k,&p,&q);
		int now=0;
		memset(dp[now],0,sizeof(dp[now]));
        dp[now][0][0]=1;
        int an=100-p-q;

        for(int i=1;i<m;i++)
        {
			int nex=now^1;
            memset(dp[nex],0,sizeof(dp[nex]));
            for(int j=0;j<n;j++)
            {
				
 
                for(int k=0;k<n-j;k++)
                {
					if(dp[now][j][k]==0) continue;
					dp[nex][min(j+1,n-1)][max(k-1,0)]=(dp[nex][min(j+1,n-1)][max(k-1,0)]+1ll*dp[now][j][k]*p%MOD)%MOD;
					dp[nex][max(j-1,0)][min(k+1,n-1)]=(dp[nex][max(j-1,0)][min(k+1,n-1)]+1ll*dp[now][j][k]*q%MOD)%MOD;

					dp[nex][j][k]=(dp[nex][j][k]+1ll*dp[now][j][k]*an%MOD)%MOD;
					
				}
            }
			now=nex;
        }
        int ans=0;
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<n-i;j++)
            {
                int tot=i+j+1;
                ans=(ans+1ll*dp[now][i][j]*po[tot][k]%MOD)%MOD;
            }
        }
        printf("%d\n",ans);
    }
}
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

typedef long long ll;
const int MAX = 501;
const int MOD = 1e9+7;

int dp[MAX][MAX][MAX];
int po[MAX][MAX];

void init()
{
    for(int i=1;i<MAX;i++){
        po[i][0]=1;
        for(int j=1;j<MAX;j++){
            po[i][j]=1ll*po[i][j-1]*i%MOD;
        }
    }
}

int main()
{
    int t;
    scanf("%d",&t);
    init();
    while(t--){
        int n,m,k,p,q;
        scanf("%d%d%d%d%d",&n,&m,&k,&p,&q);
		memset(dp[1],0,sizeof(dp[1]));
        dp[1][0][0]=1;
        int an=100-p-q;

        for(int i=1;i<m;i++)
        {
            memset(dp[i+1],0,sizeof(dp[i+1]));
            for(int j=0;j<n;j++)
            {
				int ja,js;
				ja=(j+1)%n;
				js=(j-1+n)%n;

                for(int k=0;k<n-j;k++)
                {
					if(dp[i][j][k]==0) continue;
					int ka,ks;
					ka=(k+1)%n;
					ks=(k-1+n)%n;
					
					if(k==0&&j+k+1<n) dp[i+1][j+1][0]=(dp[i+1][j+1][0]+1ll*dp[i][j][k]*p%MOD)%MOD;
					else dp[i+1][ja][ks]=(dp[i+1][ja][ks]+1ll*dp[i][j][k]*p%MOD)%MOD;
					if(j==0&&j+k+1<n) dp[i+1][0][k+1]=(dp[i+1][0][k+1]+1ll*dp[i][j][k]*q%MOD)%MOD;
					else dp[i+1][js][ka]=(dp[i+1][js][ka]+1ll*dp[i][j][k]*q%MOD)%MOD;

					dp[i+1][j][k]=(dp[i+1][j][k]+1ll*dp[i][j][k]*an%MOD)%MOD;
					
				}
            }
        }
        int ans=0;
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<n-i;j++)
            {
                int tot=i+j+1;
                ans=(ans+1ll*dp[m][i][j]*po[tot][k]%MOD)%MOD;
            }
        }
        printf("%d\n",ans);
    }
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值