AcWing 1230 K倍区间(有意思的取模技巧)

K倍区间(有意思的取模技巧)

标签(空格分隔): 题解


原题链接

给定一个长度为 \(N\) 的数列,\(A_1, A_2, … A_N\),如果其中一段连续的子序列 \(A_i, A_{i+1}, … A_j\) 之和是 \(K\) 的倍数,我们就称这个区间 \([i, j]\)\(K\) 倍区间。

你能求出数列中总共有多少个 \(K\) 倍区间吗?

输入格式

第一行包含两个整数 \(N\)\(K\)

以下 \(N\) 行每行包含一个整数 \(A_i\)

输出格式

输出一个整数,代表 \(K\) 倍区间的数目。

数据范围

\(1 \le N, K \le 100000\),
\(1 \le A_i \le 100000\)

输入样例:

5 2
1
2
3
4
5

输出样例:

6

思路

其实一开始我是在想 \(nlogn\)\(n \sqrt n\) 的做法来着, 但实际复杂度始终都在 \(n^2\) 上。(也可能是我太菜了……

说下正解 \(O(n)\) 的做法。
序列中的任意一段区间都可以用两个前缀和元素相减得到,之后思考如何在求前缀和的过程中 \(O(1)\) 求当前位置产生的贡献。

合理的取模就可以做到这一点,求出序列在模 \(k\) 意义下的前缀和数组。当前位置 \(sum_i\mod k = w\) ,当前点 \(i\) 和任意前方为 \(sum_j\mod k = w\) 的点构成的区间都是《和为 \(k\) 的倍数》 的合法区间。

同时需要注意的是,如果此时前缀和 = 0 的时候,当前点同样也是个合法的区间, 答案需要额外加上这些孤点的贡献。

code

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <set>
#define int long long 
using namespace std ; 
const int maxn = 1e5 + 7 ;
/*
int n , k , cnt ;
int a[maxn] , sum[maxn] , ak[maxn] ; 
signed main()
{
    cin >> n >> k ; 
    for(int i = 1 ; i <= n ; i++ )  
        cin >> a[i] ;
    for(int i = 1 ; i <= n ; i++ )
    {
        sum[i] += sum[i - 1] ;
        q.insert(sum[i] ) ;
    }
    for(int i = k ; i <= sum[n] ; i+=k )
        ak[++cnt] = i ; 
    for(int i = 1 ; i <= n ; i++ )
    {
        auto it = lower_bound(ak + 1 , ak + cnt + 1 , sum[i] ) ;
        now = *it ;
        while()
        {
            if(q.find(sum[i] - now) != EOF )
            {
                --it ; 
            }
        }
    }
}*/
// 看到这题一直在想nlog 或 n sqrt 的算法。
//这题的这种解题方式,以前从未见过。很妙
int n , k , ans ;
int sum[maxn] , mp[maxn] ; 
signed main()
{
    cin >> n >> k ; 
    for(int i = 1 ; i <= n ; i++ )
    {
        cin >> sum[i] ;
        sum[i] += sum[i - 1] ;
        sum[i] %= k ; 
        ans += mp[sum[i]] ; 
        mp[sum[i]]++ ;
    }
    ans += mp[0] ;   
    cout << ans ; 
}
posted @ 2022-11-11 10:50  Simon_...sun  阅读(26)  评论(0编辑  收藏  举报