转载请注明出处,谢谢javascript:void(0) by---cxlove
题意:给出一个环,每个点是一个数字,取一个子串,使得拼接起来的数字是K的倍数。
由于K不大,暂且不考虑环的话,那么dp[i][j]表示以i结尾的,模K为j的有多少个子串。
那么sigma (dp[i][0])便是不考虑环的答案。
考虑环的话,不知道别人怎么写的,我感觉我的写法不是很复杂。
环和情况1 和n肯定是必选的,那么便是一个前缀为后缀,一个后缀为前缀拼接而成。
所以枚举某个前缀,求出前缀模K,那么枚举后缀模K的值,通过之前已经预处理过的dp值,便可以求出有多少个后缀满足为K的倍数。
但是这样可能后缀和前缀重叠了,所以我们枚举前缀的同时,依次记录后缀不同模值的个数。
随着前缀的增长,这些后缀都是和前缀重叠的。
#include <iostream> #include <cstdio> #include <cstring> #include <map> #include <vector> #include <string> #include <queue> #include <cmath> #include <algorithm> #define lson step << 1 #define rson step << 1 | 1 #pragma comment(linker,"/STACK:102400000,102400000") using namespace std; typedef long long LL; const int N = 50005; const int M = 205; int n , k , a[N] ,l[N]; int dp[2][M] , prefix[N] , fac[N << 2] , suffix[N]; int cnt[M]; int cal (int x) { int cnt = 0; while (x) x /= 10 , cnt ++; return cnt; } int main () { #ifndef ONLINE_JUDGE freopen ("input.txt" , "r" , stdin); // freopen ("output.txt" , "w" , stdout); #endif while (scanf ("%d %d" , &n , &k) != EOF) { fac[0] = 1; for (int i = 1 ; i <= (n << 2) ; i ++) fac[i] = fac[i - 1] * 10 % k; for (int i = 1 ; i <= n ; i ++) { scanf ("%d" , &a[i]); l[i] = cal (a[i]); } for (int i = 0 ; i < 2 ; i ++) { for (int j = 0 ; j < k ; j ++) dp[i][j] = 0; } dp[1][a[1] % k] = 1; LL ans = dp[1][0]; for (int i = 2 ; i <= n ; i ++) { for (int j = 0 ; j < k ; j ++) dp[i & 1][j] = 0; dp[i & 1][a[i] % k] ++; for (int j = 0 ; j < k ; j ++) { dp[i & 1][(j * fac[l[i]] + a[i]) %k] += dp[(i - 1) & 1][j]; } ans += dp[i & 1][0]; } prefix[0] = 0;suffix[n + 1] = 0; for (int i = 1 ; i <= n ; i ++) { prefix[i] = (prefix[i - 1] * fac[l[i]] + a[i]) % k; } int len = 0; for (int i = n ; i >= 1 ; i --) { suffix[i] = (a[i] * fac[len] + suffix[i + 1]) % k; len += l[i]; } len = 0; for (int i = 0 ; i < k ; i ++) cnt[i] = 0; for (int i = 1 ; i <= n ; i ++) { cnt[suffix[i]] ++; len += l[i]; int p = prefix[i]; for (int j = 0 ; j < k ; j ++) { if ((j * fac[len] + p) % k) continue; ans += dp[n & 1][j] - cnt[j]; } } printf ("%I64d\n" , ans); } return 0; }