AtCoder ABC 158 E - Divisible Substring(思维)

题目链接:https://atcoder.jp/contests/abc158/tasks/abc158_e

题意:给你一个含 n n n个数位的数,问你他有多少个子序列可以被 p p p整除。

思路:再见这题之前,我们先来证明一个引例。

假设 x 1 x 2 x 3 x 4 x 5 x_1x_2x_3x_4x_5 x1x2x3x4x5 % p = = m p == m p==m && x 1 x 2 × 1 0 3 x_1x_2 \times 10^3 x1x2×103 % p = = m p == m p==m
那么 x 3 x 4 x 5 x_3x_4x_5 x3x4x5 % p = = 0 p == 0 p==0.

证明:
x 1 x 2 x 3 x 4 x 5 x_1x_2x_3x_4x_5 x1x2x3x4x5 % p p p = (( x 1 x 2 × 1 0 3 x_1x_2 \times 10^3 x1x2×103) % p p p + ( x 3 x 4 x 5 x_3x_4x_5 x3x4x5)% p p p) % p p p
因为 x 1 x 2 × 1 0 3 x_1x_2 \times 10^3 x1x2×103 % p = = m p == m p==m,那么 x 3 x 4 x 5 x_3x_4x_5 x3x4x5 % p = = 0 p == 0 p==0.

既然如此,我们就可以把 s [ ] s[] s[]所有的前缀数 × 1 0 n − i + 1 \times 10^{n-i+1} ×10ni+1 % p \%p %p的值全部用一个数组 c n t [ ] cnt[] cnt[]记录下来这样相同模式相同的任意取两个都可以找到一个满足条件的子序列.
PS:

  1. 我们这个方法不能处理 p = = 2 ∣ ∣ p = = 5 p == 2 || p == 5 p==2p==5的情况,所以要特判,但特判2或5就很简单,直接判断 s [ i ] s[i] s[i]能不能被2或者5整除就行啦.
  2. c n t [ 0 ] cnt[0] cnt[0]要赋初值为1.

那这一题就结束啦。

AC代码:

#include <set>
#include <map>
#include <stack>
#include <queue>
#include <cmath>
#include <ctime>
#include <vector>
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

#define LL long long
#define pii pair<int,int>
#define sd(x) scanf("%d",&x)
#define slld(x) scanf("%lld",&x)
#define pd(x) printf("%d\n",x)
#define plld(x) printf("%lld\n",x)
#define rep(i,a,b) for(int i = (a) ; i <= (b) ; i++)
#define per(i,a,b) for(int i = (a) ; i >= (b) ; i--)
#define mem(a) memset(a,0,sizeof(a))
#define lson l , m , rt << 1
#define rson m + 1 , r , rt << 1 | 1
#define fast_io ios::sync_with_stdio(false)

const int INF = 1e9;
const LL mod = 1e9 + 7;
const int maxn = 2e5 + 7;

char s[maxn];
LL cnt[maxn];

LL qpow(LL a,LL b,LL p) {
    LL res = 1;
    while(b) {
        if(b & 1) res = (res * a) % p;
        a = (a * a) % p;
        b >>= 1;
    }
    return res%p;
}

int main() {
    int n,p;
    while(~scanf("%d%d",&n,&p)) {
        scanf("%s",s);
        int len = strlen(s);
        LL ans = 0;
        if(p == 2 || p == 5) {
            rep(i,0,len-1) {
                if((s[i] - '0') % p == 0) ans += 1LL * (i + 1);
            }
        } else {
            mem(cnt);
            LL sum = 0;
            cnt[0] = 1;
            rep(i,0,len-1) {
                sum = (sum * 10 + (s[i] - '0')) % p;
                LL val = (qpow(10LL,n-i-1,p) * (sum % p)) % p;
                cnt[val]++;
            }
            rep(i,0,p-1) {
                ans += cnt[i] * (cnt[i] - 1) / 2;
            }
        }
        plld(ans);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值