题目传送门:https://vjudge.net/problem/LightOJ-1134
先思考一个问题:
前缀和L与前缀和R对m取余之后余数相等,那么区间(L,R)与m有什么关系?
例子:
输 入: 1 2 3 4 5 6
前 缀 和: 1 3 6 10 15 21
前缀和并对3取余: 1 0 0 1 0 0 (编号为 1-3、2-3......就是横纵坐标)
1-3和1-4结果都是1,那说明在2-1到2-4之间加上的那个数一定为3的倍数,所以就一定存在一个子序列和为3的倍数。
但是
如果像3-2、3-3、3-5、3-6一样存在四个,任意两这个都存在一个子序列,所以就是4 * (4 - 1) / 2 。
再但是
如果前缀和对3取余后等于0,说明他自身也是3 的倍数。
所以ans = 1 + 4 * (4 - 1) / 2 + 4 = 11
int t, cnt = 0;
int a[100005], s[100005];
int i, num;
cin >> t;
while(t--)
{
memset(s, 0, sizeof(s));
int n, m;
scanf("%d%d", &n, &m);
for(i = 1; i <= n; i++)
{
scanf("%d", &num);
a[i] = (a[i - 1] + num) % m;
s[a[i]]++;
}
LL ans = 0
for(i = 0; i < m; i++)
ans += (LL)s[i] * (s[i] - 1) / 2;
printf("Case %d: %lld\n", ++cnt, ans + s[0]);
}
下面这个代码其实和上面的一样,但是技巧很棒!(想出这个的人,看到这会疯 ^-^ 哈……)
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <cmath>
#include <queue>
#include <set>
#include <map>
#define ll longlong
using namespace std;
int a[100005];
int main()
{
int T, cas = 1;
scanf("%d", &T);
while(T--)
{
memset(a, 0, sizeof(a));
int n, m, sum = 0;
ll ans = 0;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
{
int x;
scanf("%d", &x);
sum = (sum + x) % m;
ans +=(ll)a[sum];
a[sum]++;
if(sum == 0)
ans++;
}
printf("Case %d: %lld\n", cas++, ans);
}
return 0;
}