ZZNU-oj-2141:2333--【O(N)求一个数字串能整除3的连续子串的个数,前缀和数组+对3取余组合数找规律】

2141: 2333

题目描述

“别人总说我瓜,其实我一点也不瓜,大多数时候我都机智的一批“ 宝儿姐考察你一道很简单的题目。给你一个数字串,你能判断有多少个连续子串能整除3吗?

输入

多实例输入,以EOF结尾,每行一个数字串(长度<=1e6)

输出

每行一个数字串,表示能整除3的连续子串的个数

样例输入

2333
121
14533254       (随机敲了一组样例,发现了新的bug)

样例输出

6
2
10

大致思路:

  根据题意可知,时间复杂度只够跑单重循环,多重循环就炸了!

  求一次前缀和,存进dp数组!然后对3取余,分别求出0/1/2的个数为sum0和sum1和sum2;

  由面向(第三组)样例编程原理可知,其前缀和数组%3取余后的结果dp=“ 121 110 20 ”,sum0=2,sum1为4,sum2为2;

  每个前缀和%3为0,最终结果ans+=sum0,然后每两个o对应的下标i和j之间那段的和也是0(因为0-0=0),故ans+=(sum*(sum-1)/2);

  然后每两个1对应的下标i和j之间那段的和也是1(因为1-1=0),故ans+=(sum1*(sum1-1)/2);——这个式子也就是组合式:C(n=sum1,m=2);

  然后每两个2对应的下标i和j之间那段的和也是0(因为2-2=0),故ans+=(sum2*(sum2-1)/2);

数学推理:

  求出前缀和数组为sum,sumi-sumj表示i--j的数组和;

  若(sumi-sumj)%3==0, 推得sumi%3 -sumj%3==0; 枚举可知sumi和sumj同为0,1,2即可!

  遍历一遍对3取余后的sum数组即可得到0,1,2的个数为sum0和sum1和sum2!  

      ans=sum0*(sum0-1)/2+sum1*(sum1-1)/2+sum2*(sum2-1)/2+sum0;

 

面向样例编出来的代码:

 1 #include <iostream>
 2 #include<stdio.h>
 3 #include<string.h>
 4 #include<string>
 5 #include<algorithm>
 6 #include<vector>
 7 #include<queue>
 8 #include<math.h>
 9 #include<map>
10 #include<set>
11 #define ll long long
12 using namespace std;
13 #define N 1000008
14 #define lson rt<<1
15 #define rson rt<<1|1
16 
17 char s[N];
18 int a[N];
19 ll dp[N];
20 int main(){
21     s[0]='0';
22     while(scanf("%s",s+1)!=EOF){
23         int len=strlen(s);
24         memset(dp,0,sizeof(dp));
25 
26         for(int i=1;i<len;i++){
27             a[i]=s[i]-'0';
28             dp[i]=(a[i]+dp[i-1])%3;
29         }
30         ll sum0=0,sum1=0,sum2=0,ans=0;
31         for(int i=1;i<len;i++){
32             if(dp[i]==0)
33                 sum0++;
34             else if(dp[i]==1)
35                 sum1++;
36             else
37                 sum2++;
38         }
39         ans=sum0*(sum0-1)/2+sum1*(sum1-1)/2+sum2*(sum2-1)/2+sum0;
40 
41         printf("%lld\n",ans);
42     }
43 
44     return 0;
45 }
View Code(严格的数据会超int的数据范围,故用longlong)

 

posted @ 2018-08-08 10:42  山枫叶纷飞  阅读(820)  评论(0编辑  收藏  举报