codeforces 479E Riding in a Lift dp+前缀数组优化

http://codeforces.com/contest/479/problem/E
dp,我这dp弱狗都看出来了,这题dp不是很难想到,比较不容易的是如何优化,使得不会T。
题意:
给定一个启示的楼层a,有一个不能去的楼层b,对于你可以去的下一个楼层必须满足你当前楼层x与下一个要去的楼层y的距离小于x到b的距离。求出走k趟的方案数。
思路:
可以定义dp[i][j]表示第i趟的时候到第 j 楼层的方案数。
转移方程dp[i][j] += dp[i-1][k] (k = 1,….,n)
很容易写出一个O( n3 )的算法,对于每一趟枚举每一个楼层,再对于每个楼层枚举前一个楼层,判断是否可以转移。
但是发现 105 并不可搞。
优化方案:
会发现查找前一个楼层的时候需要O( n ),复杂度太高。但是仔细观察会发现对于当前的楼层,上一个楼层能转移到当前楼层的地方都很有规律,都是成段的。这样就可以运用一个比较常见的优化方法了,就是运用前缀数组来记录在前一趟中到达这个点以前的方案总数有几种。这样对于任意一个成段的满足的线段都可以求出在这个区间内的方案总数了,将转移状态的复杂度降至O(1)。此时可以分类讨论,如果当前楼层在b的左边可以求出之前转移的一段线段,右边又是一种。
在转移完之后要记得扣掉前一趟也在这个楼层之中的情况。
模运算注意事项:
这题还涉及到取模运算,在取模运算的时候要小心,如果是两个取模后的值相减再取模,很可能会出现负数,可以加上一个mod,以消除影响。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define M 5009
#define mod 1000000007
typedef long long ll;
int dp[M][M];
int pre[M];
int ans;
int n,a,b,kk;
int main()
{
    while(scanf("%d %d %d %d",&n,&a,&b,&kk)==4)
    {
        ans = 0;
        memset(dp,0,sizeof(dp));
        dp[0][a] = 1;
        for(int i = 1;i <= kk;i++)
        {
            pre[0] = 0;
            for(int j = 1;j <= n;j++)
                pre[j] = (pre[j-1]+dp[i-1][j])%mod; //记录前一趟中j以前的点(包括j)的方案总数
            for(int j = 1;j <= n;j++)
            {
                if(j == b) continue;
                if(j < b)
                {
                    dp[i][j] = (pre[(abs(j-b)-1)/2+j]+mod)%mod; //小心小于而不能等于的条件
                }
                else
                {
                    dp[i][j] = (pre[n] - pre[j-(abs(j-b)+1)/2]+mod)%mod;
                }
                dp[i][j] = (dp[i][j] - dp[i-1][j]+mod)%mod; //取模相减的时候记得要+mod以消除影响
            }
        }
        int b;
        for(int i = 1;i <= n;i++)
            ans = (ans + dp[kk][i])%mod;
        printf("%d\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值