題解:AT_abc367_d [ABC367D] Pedometer

Redamancy_Lydic發表於2024-08-18

首先肯定要單層迴圈列舉元素,然後想方法求出一個元素的所有答案。

一開始我寫了一個二分找 \(m\) 倍數的方法,發現 \(m\) 小的時候還不如暴力。

於是聯想到之前做過的一道題,可以藉助於取模的字首和陣列。

對於當前元素 \(i\),如果一個元素之前的字首和與 \(i\) 之前的字首和對 \(m\) 取餘後相同,那麼說明中間的所有元素的和一定是 \(m\) 的倍數。

有了這個思路,我們可以著手想程式碼了。首先二倍陣列斷環成鏈,然後計算字首和陣列 \(sum\)。用一個 \(cnt\) 陣列記錄字首和對 \(m\) 取模後的值為一定值的個數。

在列舉 \(i\) 的時候,我們可以從 \(n+1\) 列舉到 \(2n\)。該元素對答案的貢獻顯然就是 \(cnt_{sum_i\mod m}\)。但需要注意的是,計算過 \(i\) 的貢獻之後,\(i\) 就不能對以後的答案產生新貢獻了,同時為保證找到的數字距離 \(i\) 不超過 \(n\),我們對 \(cnt\) 陣列進行更新,具體的,我們讓 \(cnt_{sum_{i-n+1}\mod m}--\)\(cnt_{sum_{i}\mod m}++\)。這樣使得 \(cnt\) 一直表示的是 \(i-n+1\)\(i\) 之間的結果。

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read()
{
	int w=1,s=0;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-') w=-1;ch=getchar();}
	while(isdigit(ch)){s=s*10+(ch-'0');ch=getchar();}
	return w*s;
}
const int maxn=1e6+10;
const int mod=1e9+7;
int n,m;
int a[maxn];
int sum[maxn],ans=0;
map<int,int> cnt;
signed main()
{
//	freopen("xxx.in","r",stdin);
//	freopen("xxx.out","w",stdout);
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		a[i]=read();
		a[i+n]=a[i];
	}
	for(int i=1;i<=n*2;i++)
	{
		sum[i]=sum[i-1]+a[i];
	}
	for(int i=2;i<=n;i++)
	{
		cnt[sum[i]%m]++;
	}
	for(int i=n+1;i<=2*n;i++)
	{
		ans+=cnt[sum[i]%m];
		cnt[sum[i-n+1]%m]--;
		cnt[sum[i]%m]++;
	}
	cout<<ans;
	return 0;
}