动态规划专题 - 解题报告 - G

第i位之前,以i位结尾的子串,与第(i-1)位一起考虑的话,会有这样的规律,i-1位上的子串的数量乘以10+第i位的数字,就是当前串的大小,但是我们需要的不是大小,而是能否整除数字k,其实就更方便了,因为i-1上的数字取模后不影响乘积求和再取模,我们知道每一次的取模都会对后面的状态转移有影响,而且每次取模后结果都可以控制在k内,我们其实可以把每一次取模后结果为t的数量记下来,我们最后求的是t==0时的数量,整体思路就出来了。
我们创立一个二维dp[][]数组,dp[i][j]表示的是以i为末位的子串,子串取模后为j的数量。然后一路维护就行了

for(int j = 0; j < k; j++)
{
    t = (j * 10 + num) % k;			//与前一子串的求和取模的值,记录在当前子串的dp数组中
    dp[i][t] += dp[i - 1][j];			//t可由j转移而来
}
dp[i][num % k]++;			//然后本身会有一个数字,也是子串
ans += dp[i][0];				//一路统计最终结果

但是这个做法就有一个问题,很容易re,因为n和k都是不定的,要是够毒瘤的话两边都会有极限值2e7,二维数组同时2e7就是暴毙,这里有两种处理方式,一种是题解教的滚动数组,但是本题我的处理方式是直接降维,因为n和k相乘是小于2e7的,我们开一个大小为2e7的一维数组,使所有的原本的[ i ][ j ]的点都用一个单独的值对应。

#define id(a, b) (a+1)*k+b			//用id来代表i,j的一个映射
//状态转移可以写成这样:
for(int j = 0; j < k; j++)
{
 	 t	= (j * 10 + num) % k;
 	 dp[id(i, t)] += dp[id(i - 1, j)];
}
dp[id(i, num % k)]++;
ans += dp[id(i, 0)];

一样的逻辑,简单的处理,Accepted!

#include<bits/stdc++.h>
#define FOR(a, b, c) for(int a=b; a<=c; a++)
#define maxn 25000005
#define maxm 55
#define hrdg 1000000007
#define inf 2147483647
#define llinf 9223372036854775807
#define ll long long
#define pi acos(-1.0)
#define ls p<<1
#define rs p<<1|1
#define id(a, b) (a+1)*k+b
using namespace std;

ll n, k, num, ans, dp[maxn];
int x, t;

char ch;

inline ll read(){
    char c=getchar();long long x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}

int main()
{
    n = read();
    k = read();
    memset(dp, 0, sizeof(dp));
    if(k != 1)			//一点点沙雕判断,其实好像没啥卵用
        for (int i = 1; i <= n; i++)
        {
            cin>>ch;
            num = ch - '0';
            for(int j = 0; j < k; j++)
            {
                t = (j * 10 + num) % k;
                dp[id(i, t)] += dp[id(i - 1, j)];
            }
            dp[id(i, num % k)]++;
            ans += dp[id(i, 0)];
        }

    else
        ans = n * (n + 1) / 2;
    cout<<ans;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值