首页 > 试题广场 >

多多的求和计算

[编程题]多多的求和计算
  • 热度指数:7060 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 256M,其他语言512M
  • 算法知识视频讲解
多多路上从左到右有N棵树(编号1~N),其中第i个颗树有和谐值Ai。
多多鸡认为,如果一段连续的树,它们的和谐值之和可以被M整除,那么这个区间整体看起来就是和谐的。
现在多多鸡想请你帮忙计算一下,满足和谐条件的区间的数量。

输入描述:
第一行,有2个整数N和M,表示树的数量以及计算和谐值的参数。
( 1 <= N <= 100,000, 1 <= M <= 100  )
第二行,有N个整数Ai, 分别表示第i个颗树的和谐值。
( 0 <= Ai <= 1,000,000,000 )


输出描述:
共1行,每行1个整数,表示满足整体是和谐的区间的数量。
示例1

输入

5 2
1 2 3 4 5

输出

6

说明

长度为1: [2], [4]
长度为2: 无
长度为3: [1,2,3], [3,4,5]
长度为4: [1,2,3,4], [2,3,4,5]
长度为5: 无
共6个区间的和谐值之和可以被2整除。

备注:
对于50%的数据,有N<=1,000
对于100%的数据,有N<=100,000
  • JavaScript 版本
    function sum(num, param, arr) {
      let total = 0
      for (let i = 1; i <= num; i++) {
          for (let j = 0; j <= num-i; j++) {
              let temporaryArr = arr.slice(j, j+i)
              if (eval(temporaryArr.join('+')) % param === 0) total++
          }
      }
      return total
    }
发表于 2021-05-20 16:57:33 回复(1)
计算数组的前缀和,并以前缀和与m的余数remain为key,余数为remain的前缀和个数为value构建hash表。由于相同余数的前缀区间任选两个所构成的中间区间一定和谐(因为大的那个前缀区间求和减去小的前缀区间求和,刚好把那个多出来的余数减掉了,因此中间区间求和一定能被m整除),所以对所有key的value求取2的组合数,把它们都加起来就能够得到所有和谐区间的总数。
而C2n=(n-1)*n/2恰好就是0~n-1的高斯求和公式,因此在计算value值的时候我们就可以顺便通过累加把总数给求了,从而省掉之后遍历hash表所有key计算组合数的时间。
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String[] params = br.readLine().split(" ");
        int n = Integer.parseInt(params[0]);
        int m = Integer.parseInt(params[1]);
        int[] map = new int[m];       // 除以m的余数为0~m-1
        params = br.readLine().split(" ");
        int[] A = new int[n];
        map[0] = 1;
        long culSum = 0L;
        long count = 0L;
        for(int i = 0; i < n; i++) {
            A[i] = Integer.parseInt(params[i]);
            culSum += A[i];
            int remain = (int)(culSum % m);
            count += map[remain];
            map[remain] ++;
        }
        System.out.println(count);
    }
}

编辑于 2021-08-29 19:26:27 回复(1)
#include <iostream>
#include <cstring>
using namespace std;
int main(){
    int n, m;
    cin>>n>>m;
    int arr[n];
    for (int i = 0; i < n; ++i){
        cin>>arr[i];
    }
    int map[m];//下标表示前缀和 % m后的值,map[i]表示取余后值为i的个数;
    memset(map, 0, sizeof(map));
    map[0] = 1;//需要设置这个初始值,看下面代码就懂了
    long sum = 0;
    long ans = 0;
    for (int i = 0; i < n; ++i){
        sum += arr[i];
        int index = sum % m;
        ans += map[index]++;
    }
    cout<<ans<<endl;
    return 0;
}

发表于 2021-05-12 16:38:57 回复(14)
Python3
n, m = map(int, input().split())
tree = list(map(int, input().split()))
d = {0: 1}
s, r = 0, 0
for i in range(n):
    r = (r + tree[i]) % m
    s += d.get(r, 0)
    d[r] = d.get(r, 0) + 1
print(s)


编辑于 2021-10-29 07:34:02 回复(0)
java一定主要超限的问题,用Long!!!思路就是先前缀和,用hashmap存一下模M相等的数,模M相等的数中任取两个即可满足条件,故有val*(val-1)/2
import java.util.*;
public class Main{
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt();
        int M = sc.nextInt();
        int[] ais = new int[N];
        int[] sums = new int[N+1];
        for(int i=0;i<N;i++){
            ais[i] = sc.nextInt();
            sums[i+1] = (sums[i]+ais[i])%M;
        }
        Map<Integer,Long> map = new HashMap<>();
        for(int i=0;i<=N;i++){
            int local = sums[i]%M;
            map.put(local,map.getOrDefault(local,0l)+1l);
        }
        long res = 0;
        for(long val:map.values()){
            res += val*(val-1)/2;
        }
        System.out.println(res);
    }
}


发表于 2021-09-18 14:52:57 回复(0)
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
ll n,m,a,sum=0,v[1005],ans;
int main()
{
    ios::sync_with_stdio(0),cin.tie(0);
    int i,j;
    cin>>n>>m;
    v[0]=1;/**< 用v数组统计每个位置前缀和余数的个数,相同余数的区间必然能被m整除 */
    for(i=1;i<=n;i++)
    {
        cin>>a;
        sum=(sum+a)%m;
        v[sum]++;
    }
    for(i=0;i<m;i++)/**< 相同余数任选2个都可以和谐区间 */
        ans+=v[i]*(v[i]-1)/2;
    cout<<ans;
    return 0;
}


发表于 2021-06-27 10:18:50 回复(0)
C++实现,利用前缀和转换
#include <iostream>
#include <vector>
using namespace std;

int main() {
    long long n, m; 
    cin >> n >> m;
    vector<long long> cnt(m, 0);  //前缀和余数出现的次数
    cnt[0] = 1;   // 数值未加入前,前缀和为0,统计起点为0的情况
    //前缀和sum[j] - sum[i]为区间[i, j]的总和
    //所以统计前缀和余数相同的数出现的次数,任意选两个可以组成一个和谐区间
    long long tree; 
    long long sum = 0;
    for(int i = 0; i < n; i ++){
        cin >> tree;
        tree = tree % m;
        sum = (sum + tree) % m;
        cnt[sum] ++;
    }
    long long ans = 0;
    for(int i = 0; i < m; i ++){
        ans += cnt[i] * (cnt[i] - 1)/2;
    }
    cout << ans << endl;
    return 0;
}

发表于 2023-03-09 11:36:06 回复(0)

O(n2)算法

区间的长度范围为[1,arr.length]。对于每个区间长度,遍历数组判断区间是否满足条件
private static int f(int[] arr,int mod){
    int n = arr.length,count=0;
    
    //c代表区间长度
    for(int c=1;c<=n;c++){
    
        //区间开始结束值以及区间和的大小
        int end=0,start=0;
        long sum=0;
        while (end<c-1){
            sum+=arr[end++];
        }
        
        //从左到右“滑动区间”并计算是否满足条件
        while (end<n){
            sum+=arr[end++];
            if(sum%mod==0){
                count++;
            }
            sum-=arr[start++];
        }
    }
    return count;
}
复杂度为O(n2),只过了60%测试用例就超时了:

如何优化?

前缀和

假设有prefix[]数组,那么sum[i,j]就等于prefix[j]-prefix[i-1]
那么如果按照上面的框架,代码会变成:
private static int f(int[] arr,int mod){
    long[] prefix=new long[arr.length+1];
    int k=1;
    for(int i : arr){
        prefix[k]=prefix[k-1]+i;
        k++;
    }

    int n = arr.length,count=0;
    for(int c=1;c<=n;c++){
        int start=0;
        int end = start+c-1;
        if(end<n){
            while(end<n){
                if((prefix[end+1]-prefix[start])%mod==0){
                    count++;
                }
                start++;end++;
            }
        }else {
            break;
        }
    }
    return count;
}
复杂度还是O(n2)==仍然过不了


做卷的时候我就思考到了这一步==因为数组并不有序并不为等差,我认为前缀和优化没有用就没做了==

前缀和直接取模

==感觉不可能想得到==反正不看答案我是没想到==
核心公式:

据此,我们可以计算所有prefix[i]%m的值,以该值作为key(表达式的x),value为所有满足prefix[i]%m==x的prefix的个数。
这样,可以确保,每个key下面任何两个前缀和取出来计算得到的区间都满足要求(即右侧表达式)
debug--要用long才行
private static long f(int[] arr,int mod){
    long[] prefix=new long[arr.length];
    prefix[0]=arr[0];
    Map<Long,Long> map = new HashMap<>();
    map.put(prefix[0]%mod,1L);
    for(int i=1;i<arr.length;i++){
        prefix[i]=prefix[i-1]+arr[i];
        map.put(prefix[i]%mod,map.getOrDefault(prefix[i]%mod,0L)+1);
    }
    return map.values().stream().mapToLong(n->n*(n-1)/2).sum()
        +map.getOrDefault(0L,0L);
    //需要加上单值区间直接可以mod到0的情况
}
复杂度O(n),啪的一下就完了








发表于 2022-07-24 10:49:47 回复(1)
# 利用前缀和原理
n,m = list(map(int, input().strip().split(' '))) # 主要在意M就行,要能被M整除
ai=list(map(int,input().strip().split(' '))) # 每个数组的值
li={0:1} # li字典放所有的前缀和 除 m 得到的余数出现的次数,初始化我们需要 使出现 余数为 0 出现一次
sum=0
count = 0 # 用来统计
for i in ai:
    sum+= i # 前缀和
    yu=sum%m # 求前缀和的除m的余数
    if yu in li.keys():
        # 如果 已有存在的key(余数)说明到他这里我们必有可以除断的。(可以理解为余数一样的那么中间这一段必定可以除断)
        # 然后我们需要看几个余数一样的来判断可以组合几个
        count += li[yu]
    if yu not in li.keys(): # 将前缀和的余数存进字典
        li[yu]=1
    else:
        li[yu]+=1
print(count)

发表于 2021-09-24 10:01:47 回复(0)
import java.util.*;
 
public class Main{
    public static void main(String[] args){
        Scanner scanner = new Scanner(System.in);
        int N = scanner.nextInt();
        int M = scanner.nextInt();
        int[] A = new int[N];
        for(int i = 0; i < N; i++){
            A[i] = scanner.nextInt();
        }
        int count = 0;
        int sum = 0;
        Map<Integer, Integer> record = new HashMap<>();
        //遍历数组nums之前,record提前放入 0:1 键值对,代表求第 0 项前缀和之前,前缀和 mod K 等于 0 这种情况出现了 1 次。
        record.put(0,1);
        for(int elem : A){
            sum+=elem;//前缀和
            int model = (sum % M + M) % M;//取余数
            //getOrDefault() 方法获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值0。
            record.put(model, record.getOrDefault(model, 0)+1);
        }
        //values() 方法返回映射中所有 value 组成的 Set 视图
        for(int n : record.values()){
            count+=n*(n-1)/2;
        }
        //for(Map.Entry<Integer, Integer> entry : record.entrySet()){
        //    count+=entry.getValue()*(entry.getValue()-1)/2;
        //}
        System.out.println(count);
    }
}

发表于 2021-05-21 22:18:05 回复(0)
var firstline=readline().split(" ")
var listlen=parseInt(firstline[0])
var harmonum=parseInt(firstline[1])
var list=readline().split(" ")
var lista=list.map(item=>parseInt(item))
//存放0-i位置的数组和的余数
var leftnum=[]
var sumtmp=0
var ans=0
//存放某个余数的出现个数
var map=new Map([[0,0]])
for(var i=0;i<listlen;i++){
    sumtmp+=lista[i]
    if(sumtmp%harmonum==0){ans+=1}
    if(!map.has(sumtmp%harmonum)){
        map.set(sumtmp%harmonum,1)
    }else{
        ans+=map.get(sumtmp%harmonum)
        map.set(sumtmp%harmonum,map.get(sumtmp%harmonum)+1)
    }
}
console.log(ans)
js版本

发表于 2022-04-11 16:11:38 回复(0)
python版本,供参考
n, m = map(int, input().split())
inp = input().split()
 
for i in range(len(inp)):
    inp[i] = int(inp[i])
 
sum1 = 0
 
for i in range(1, n+1):
    for j in range(n-i+1):
        if sum(inp[j: j+i]) % m == 0:
            sum1 += 1
 
print(sum1)


发表于 2022-03-16 11:07:02 回复(0)
n,m = list(map(int, input().strip().split(' ')))
ai=list(map(int,input().strip().split(' ')))
li={}
sum=0 for i in ai:
    sum+=i
    yu=sum%m if yu not in li.keys():
        li[yu]=1  else:
        li[yu]+=1 hh=0 for i,j in li.items(): if i==0:
        hh+=j
    nn=j*(j-1)//2  hh+=nn print(hh)
发表于 2021-05-11 00:02:47 回复(0)
动态规划:
import java.util.*;
 import java.io.*;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        while (in.hasNextInt()) { // 注意 while 处理多个 case
            int N = in.nextInt();
            int M = in.nextInt(); in.nextLine();
            // BufferedWriter wirter = new BufferedWriter(new OutputStreamWriter(System.out));
            String[] itt = in.nextLine().split(" ");
            int[] dend = new int[N+1];

            long res =0L;
            for(int idx=N-1;idx>=0;idx--){
                int mod=0;
                for(int jdx=idx;jdx<N;jdx++){
                    mod=(mod+Integer.parseInt(itt[jdx]))%M;
                    if(mod==0){
                        dend[idx]=1+dend[jdx+1];
                        break;
                    }
                }
                res+=dend[idx];
            }
            System.out.println(res);
        }
    }

}

编辑于 2024-03-14 23:21:16 回复(0)
import java.util.*;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static long countHarmonySubarrays(long[] A, int M) {
        long count = 0;
        long[] P = new long[A.length + 1];
        Map<Long, Long> map = new HashMap<>();
        for (int i = 0; i < A.length; i++) {
            P[i + 1] = P[i] + A[i];
            if (P[i + 1] % M == 0) {
                count++;
            }
            if (map.containsKey(P[i + 1] % M)) {
                count += map.get(P[i + 1] % M);
            }
            if (!map.containsKey(P[i + 1] % M)) {
                map.put(P[i + 1] % M, 0L);
            }
            map.put(P[i + 1] % M, map.get(P[i + 1] % M) + 1);
        }
        return count;
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int N = scanner.nextInt();
        int M = scanner.nextInt();
        long[] A = new long[N];
        for (int i = 0; i < N; i++) {
            A[i] = scanner.nextLong();
        }
        System.out.println(countHarmonySubarrays(A, M));
    }
}

发表于 2023-03-12 16:05:29 回复(0)
借鉴评论区版 js
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;

// 相同余数的前缀区间任选两个所构成的中间区间一定和谐(因为大的那个前缀区间求和减去小的前缀区间求和,刚好把那个多出来的余数减掉了,因此中间区间求和一定能被m整除)
void async function () {
    // Write your code here
    var arr=[];
    while(line = await readline()){
        arr.push(line)
        
    }
    // console.log("arr:",arr)
    var firstline=arr[0].split(" ")
    // console.log("firstline:",firstline)
    var listlen=parseInt(firstline[0])
    var harmonum=parseInt(firstline[1])
    var list=arr[1].split(" ")
    var lista=list.map(item=>parseInt(item))
    // console.log('lista:',lista)
// //存放0-i位置的数组和的余数
    // var leftnum=[]
    var sumtmp=0
    var ans=0
// //存放某个余数的出现个数
    var map=new Map([[0,0]])
    for(var i=0;i<listlen;i++){
        sumtmp+=lista[i]
        if(sumtmp%harmonum==0){ans+=1}
        if(!map.has(sumtmp%harmonum)){
            map.set(sumtmp%harmonum,1)
        }else{
            ans+=map.get(sumtmp%harmonum)
            map.set(sumtmp%harmonum,map.get(sumtmp%harmonum)+1)
        }
    }
    console.log(ans)
}()

发表于 2023-02-22 15:57:58 回复(0)
这里一般暴力的方法都是两次循环,但是从时间复杂度的角度来看肯定不可能通过,所以最通用的方法就是通过观察消减第二次循环的循环次数,这里就是一个很经典的消减的方法!!!
要记住并且经常使用这个方法
将第二次循环的N次循环,消减为更小的循环M次
#include<bits/stdc++.h>
using namespace std;
int main(){
    long int n = 0, m = 0;
    cin>>n>>m;
    vector<long int> vec(n);
    for(auto &i : vec) cin>>i;
    vector<long int> arr(m, 0);
    long long int ans = 0;
    for(auto i : vec){
        vector<long int> old = arr;
        for(int j = 0;j < m;j++){
            arr[(i + j)%m] = old[j];
        }
        arr[i%m]++;
        ans += arr[0];
    }
    cout<<ans;
    return 0;
}

发表于 2022-09-22 17:40:54 回复(0)
#include <iostream>
#include<vector>
using namespace std;


int main()
{
	int N, M;
	cin >> N >> M;
	vector <int> arr(N);
	for (int i = 0; i < N; i++)
	{
		cin >> arr[i];
	}
	vector <int> map(M,0);
	map[0] = 1;
	long sum = 0;
	long ans = 0;
	for(int i = 0; i < N; i++)
	{
		sum += arr[i];
		int index = sum % M;
		ans += map[index]++;
	}
	cout << ans << endl;
	system("pause");
	return 0;

}

发表于 2022-06-05 16:13:48 回复(0)
动态规划思路
dp[i]: 以i结尾的满足条件的连续子数组数量
trees[]:树的和谐值
// 动态规划

import java.util.*;

public class Main {
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		
		while (in.hasNextLine()) {
			String[] s1 = in.nextLine().trim().split(" ");
			String[] s2 = in.nextLine().trim().split(" ");
			
			int N = Integer.parseInt(s1[0]);
			int M = Integer.parseInt(s1[1]);
			int[] trees = new int[s2.length];
			for (int i = 0; i < s2.length; i++) {
				trees[i] = Integer.parseInt(s2[i]);
			}
			
			long ans = solution(N, M, trees);
			System.out.println(ans);
		}
	}
	
	private static long solution(int N, int M, int[] trees) {
		
		// dp[i]: 以i结尾的满足条件的连续子数组数量
		int[] dp = new int[N];
		
		// 初始化
		if (trees[0] % M == 0) {
			dp[0] = 1;
		}
		
		// 递推
		long ans = dp[0];
		for (int i = 1; i < N; i++) {
			// 递推公式
			if (trees[i] % M == 0) {
				dp[i] = dp[i-1] + 1;
			} else {
				long sumVal = trees[i];
				for (int j = i-1; j >= 0; j--) {
					sumVal += trees[j];
					if (sumVal % M == 0) {
						if (j-1 < 0) {
							dp[i] = 1;
						} else {
							dp[i] = dp[j-1] + 1;
						}
						break;
					}
				}
			}
			ans += dp[i];
		}
		
		return ans;
	}
}

发表于 2022-06-04 17:05:58 回复(0)

前缀和技巧 + 同余

#include <iostream>
#include <algorithm>
#include <vector>
#include <unordered_map>
// #include <bits/stdc++.h>

using namespace std;
const int N = 1e5 + 5;
int w[N];
unordered_map<int, int> cnt;

void solve(){
    cnt[0] = 1;

    int n, m;
    cin >> n >> m;
    int s = 0;
    long long ans = 0;
    for (int i = 1; i <= n; i ++ ) {
        int x;
        cin >> x;
        s = (s + x) % m;
        ans += cnt[s];
        cnt[s] ++ ;
    }

    cout << ans << endl;
}

int main()
{   
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int t;
    // cin >> t;
    t = 1;
    while (t -- )
        solve();
    return 0;
}
发表于 2021-10-16 13:15:38 回复(0)