这是我写题解以来,写的字数最多,最详细的一篇了
知识点很少,仅仅只是对y总三行代码的解释。
如果你已经可以自己完成这道题目了,我劝君还是莫要看这篇题解了,因为我都觉得罗嗦了。
知识点前列和大家都学的很好,所以我这里从前列和后开始说起
先给出我的样例:
n = 9,k = 4, a[1~9]:1, 2, 3, 4, 5, 6, 7, 8, 9//这是输入的数据
s[10] = {0, 1, 3, 6, 10, 15, 21, 28, 36, 45};//这是前列和
s1[10] = {0, 1, 3, 2, 2, 3, 1, 0, 0, 1};//这是每一项前列和对k = 4取模运算的结果
我们最开始的解题思路是这样的:
for (int R = 1; R <= 9; R ++ )//我用R代表区间右端点,(用i不好看*……*)
for (int L = 1; L <= R; L ++ )//L表示区间的左端点
if ((s[R] - s[L - 1]) % k == 0)//这一步就是判断这个区间段元素之和是否是k的倍数
res ++;//如果是就+1
//这一个代码大家都了解,就是最朴素的解题思想
//然后我们开始进行第二步:转化1
我们用r替换R,l替换L - 1(等价于L = l + 1)
for (int r = 1; r <= 9; r ++ )
for (int l = 0; l <= r - 1; l ++ )
if ((s[r] - s[l]) % k == 0)
res ++;
//这个应该是高中的换元思想,这是一个等价的变形
//然后我们开始进行第三步:转化2
因为if ((s[r] - s[l]) % k == 0)这一步是判断区间[l+1, r]这个区间的和是否是k的倍数
则,if ((s[r] - s[l]) % k == 0)与if(s[r] % k == s[l] % k)是等价的
我们可以再次进行变形:
给每个s[] 去模上k,这就有了我们的s1[],记录s[]取模后的值。
那么我们的公式就可以在进行变形了
【变形开始】
for (int r = 1; r <= 9; r ++ )
for (int l = 0; l <= r - 1; l ++ )
if ((s1[r] == s1[l]))
res ++;
但是当代码简化到这里的时候,我们突然会有一种代码变得很罗嗦的感觉,这是为什么呢
啊!我们找了一圈,就是在找这个(s1[r] == s1[l]),就是找让这两个东东,他们的值相等
法外狂徒张三表示很淦!!! 张三都看不下去了(&^&)
为了守护我们张三同志,我们必须把它进行优化.
好不就是用了来来回回的找(s1[r] == s1[l])吗,那我就打表找规律。
因为我们看的是(s1[r] == s1[l]),我们就将数据抽出来专门看这个
已知s1[10] = {0, 1, 3, 2, 2, 3, 1, 0, 0, 1};,我们的代码进行的就是将他循环两边,并且在找
(s1[r] == s1[l])。那我们可以这样淦!
进行枚举的时候每次都判断一次太麻烦了,我们把if条件成立的数据给拿出来,单看看!
比如我们就拿s1[] = 1来看, 一共有三个1
好!
int ss[4] = {0,1,1,1}
for (int r = 1; r <= 3; r ++ )
for (int l = 0; l <= 3 - 1; l ++ )
if (ss[r] == ss[l])
res ++;
我们就得到了一个公式,令number为s1中的某个元素的个数,则
res = number * (number - 1) / 2
那么我们只要将每个元素的个数都给找出来,然后套用一下这个公式就OK了
但是一定要注意,找元素0的个数时,千万别忘记+1,为啥,因为s[0] = 0;
千万不要忽略,想想看我们用了多少次s[L == 1],及s[l == 0]运算,可不能提了裤子不认人啦!
这里我们简化的太厉害了,简直就是直击本质呀,但是简化的太厉害了,都过了头了,我们要是
还要再求一遍每个元素的个数,那就舍近求远了
所以下面我就分析一下y总教给我们的代码
LL res = 0;//res用来计数
cnt[0] = 1;//就是我们前面讲的,一定要先给cnt[0] 加上一个1,因为cnt时来计数的
//s[0] = 0就是一个
for (int i = 1; i <= n; i ++ )
{
res += cnt[s[i] % k];
cnt[s[i] % k] ++ ;
/*
这里像不像我们上面的res = number * (number - 1) / 2
只不过它是一个大杂烩,将所有的数都给放到一起加了。
其实和我们上面的解释差不多
如果我们将相同元素抽出来看(res就是相当于给cnt[]重新排个序加起来。
然后再全部加起来。
res负责将数据全部加起来
而cnt[],你看像不像是把我们分析里的res的乘法变成了y总代码里的加法
我们重新拍个顺序看一下
当s[i] % k == 0 时, cnt[s[i] % k]的变化为:1->2->3
当s[i] % k == 1 时, cnt[s[i] % k]的变化为:0->1->2
当s[i] % k == 2 时, cnt[s[i] % k]的变化为:0->1
当s[i] % k == 2 时, cnt[s[i] % k]的变化为:0->1
除了顺序变了以外,cnt的值的变化就是这个样子,没毛病
然后我们交给res都给加起来,
不就是我们分析的里面的:
res1 = 3*(3 - 1) / 2 = 3;
res2 = 3*(3 - 1) / 2 = 3;
res3 = 2*(2 - 1) / 2 = 1;
res4 = 2*(2 - 1) / 2 = 1;
res = res1 + res2 + res3 + res4 = 3 + 3 + 1 + 1 = 8
*/
/*
如果质疑这个结论的正确定,或者还是没有理解,我们再来模拟一次,
模拟一次例题给的样例
n = 5, k = 2;
a[6] ={1, 2, 3, 4, 5};
s[6] = {0, 1, 3, 6, 10, 15};
s1[6] = {0, 1, 1, 0, 0, 1}
我们就不验证y总的代码了,验证一下我的结论
当s[i] % k == 0 时, cnt[s[i] % k]的变化为:0->1->2
当s[i] % k == 0 时, cnt[s[i] % k]的变化为:0->1->2
res1 = 3*(3 - 1) / 2 = 3;
res2 = 3*(3 - 1) / 2 = 3;
res = res1 + res2 = 3 + 3 = 6;
完美!!!
*/
}
下面时我的代码,但几乎和y总的代码一摸一样,所以不看也好
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 100010;
int n, k;
ll s[N], cnt[N];
int main(){
cin >> n >> k;
for (int i = 1; i <= n; i ++ ) scanf("%d", &s[i]), s[i] = (s[i] + s[i -1]) % k;
ll res = 0;
cnt[0] = 1;
for (int i = 1; i <= n; i ++ ){
res += cnt[s[i]];
cnt[s[i]] ++;
}
cout << res << endl;
return 0;
}
给您的认真orz一下,虽然我没看过这题
感谢感谢,hhhhhhhhh
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%