[ACM]【prefix/组合数计数/同余】Atcoder164 Multiple of 2019

Multiple of 2019

传送门
题意:给一个巨长的数列,要求找出是2019的倍数的片段数目。

在这里插入图片描述

思路:

我就知道是前缀和同余.…但是菜鸡如我只想出了O(n^2)的方法。一直纠结在这个10怎么办,TLE还觉得是输入有问题
正解的思路:
开一个数组 p r e f [ i ] pref[i] pref[i],记录: a n + 1 0 1 × a n − 1 + 1 0 2 × a n − 2 + . . . + 1 0 n − i × a i a_n+10^1\times a_{n-1}+10^2\times a_{n-2}+...+10^{n-i}\times a_i an+101×an1+102×an2+...+10ni×ai。这样一来, ( i , j ) (i,j) (i,j)之间的数字就是 ( p r e f [ i ] − p r e f [ j + 1 ] ) ÷ 1 0 n − j (pref[i]-pref[j+1])\div 10^{n-j} (pref[i]pref[j+1])÷10nj。要使得 ( i , j ) m o d 2019 = 0 (i,j)mod2019=0 (i,j)mod2019=0,只需要 ( p r e f [ i ] − p r e f [ j + 1 ] ) m o d 2019 = 0 (pref[i]-pref[j+1])mod2019=0 (pref[i]pref[j+1])mod2019=0(因为10的因子都不是2019的因子,所以不考虑外面的10) p r e f [ i ] pref[i] pref[i] p r e f [ j + 1 ] pref[j+1] pref[j+1]同余。那么只用计算同余的组合数就可以了。
怎么得到 p r e f [ i ] pref[i] pref[i]数组呢?只需要后一个 p r e f [ i + 1 ] pref[i+1] pref[i+1]加上 1 0 n − i × s [ i ] 10^{n-i}\times s[i] 10ni×s[i]即可。
那怎么计算组合数呢? 因为组合数的公式为 n × ( n − 1 ) 2 \frac{n\times (n-1)}{2} 2n×(n1),正好等于等差数列 1 + 2 + . . . + ( n − 1 ) 1+2+...+(n-1) 1+2+...+(n1)的和。所以就可以线性计算了。(见代码)
还要注意,如果计算 p r e f [ i ] pref[i] pref[i]的时候出现了 0 0 0,那他自己就可以整除。每出现一次这样的情况,组合数就相当于加上了现存的 c n t [ 0 ] cnt[0] cnt[0],等价于一开始就初始化为1.

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=200004;
int rmd[maxn];//记录remainder
int pref[maxn];
char s[maxn];
int main(){
	scanf("%s",s+1);
	int base=1;
	long long cnt=0;
	rmd[0]=1;//注意
	int len=strlen(s+1);
	for(int i=len;i>=1;i--){
		pref[i]=(pref[i+1]+base*(s[i]-'0'))%2019;
		base=base*10%2019;
		cnt+=rmd[pref[i]];//计算组合数
		rmd[pref[i]]++;
	}
	printf("%lld\n",cnt);
}

下面是令人窒息的菜鸡TLE操作(((

#include<bits/stdc++.h>
using namespace std;
int mod[200004];
int ten[200004];
int main(){
	int len=0;
	ten[0]=1;
	char ch = getchar();
    while(!isdigit(ch)) ch = getchar();
    while(isdigit(ch)) {
		int tmp=ch-'0';
		ten[++len]=10*ten[len-1]%2019;
		mod[len]=(tmp%2019+mod[len-1]*10%2019)%2019;
        ch = getchar();
    }
	long long ans=0;
	for(int i=1;i<=len;i++){
		for(int j=i+1;j<=len;j++){
			if((mod[j]+2019-mod[i-1]*ten[j-i+1])%2019==0){
				ans++;
			}
		}
	}
	printf("%lld\n",ans);
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值