第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;
}