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 ;
}