算法分析
朴素做法 $(n^2)$
for(int R = 1;i <= n;R ++) //右端点
for(int L = 1;L <= R;L++)//左端点
{
int sum = s[R] - s[L - 1];
if(sum % k == 0) res ++;
}
-
1、当
R
固定时,在1 ~ R之间找到有多少个L,满足(s[R] - s[L - 1]) % k == 0
即 当R
固定时,在1 ~ R - 1之间找到有多少个L,满足(s[R] - s[L]) % k == 0
-
2、使用空间换时间的优化,通过一个
cnt[]
数组,来记录余数是i
的数有多少个
可优化到$O(n)$
for(int R = 1;R <= n;R ++)
{
res += cnt[s[R] % k];
cnt[s[R] % k] ++;
}
时间复杂度 $O(n)$
Java 代码
import java.util.Scanner;
public class Main {
static int[] s = new int[100010];//记录前缀和
static int[] cnt = new int[100010];//记录余数是i的数有多少个
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();
int k = scan.nextInt();
for(int i = 1;i <= n;i++)
{
s[i] = (s[i - 1] + scan.nextInt()) % k;
}
long res = 0;
cnt[0] = 1;//余数为0时,已经占一个
for(int i = 1;i <= n;i++)
{
res += cnt[s[i]];
cnt[s[i]] ++ ;
}
System.out.println(res);
}
}
大佬 暴力第一行应该是R<=n吧
cnt[0] = 1
是当只有一个数(n = 1) 且这个数是k倍区间 这时要加上它本身 应该是这样理解吧大佬能解释下, cnt[0] = 1;//余数为0时,已经占一个 这个是什么意思么,没搞懂
因为第一个前缀和的余数是
0
的位置t
的时候,从[1,t]
的累加和是k
的倍数,可以直接当做1
个,如果第一个前缀和的余数是1
的位置t
的时候,那么[1,t1]
的累加和就不算是k
的倍数,这里就只能当做是0
个,必须从第二个余数是1
的位置t2
开始,[t1 + 1,t2]
的累加和才算第一个k
的倍数我大概懂了,谢谢大佬解答
懂了,感谢
res += cnt[R % k];
cnt[R % k] ++; 可以举个例子解释一下这两句吗 我还是看不懂 R是前缀和然后取模于k得到余数 为什么res +=这个东西
修正了一下代码,由于
(s[R] - s[L]) % k == 0
,即s[R] % k = s[L] % k
,有多少个s[L]
与s[R]
同时模k的余数相同,打个比方说,有一个余数是5
,本来余数是5
的个数是3
,那么后面又出现了余数是5
的数,那么第1
个个数和第4
个个数形成了3k
的区间,第2
个个数和第4
个个数形成了2k
的区间,第3
个个数和第4
个个数形成了1k
的区间,即多了3
个k
倍的区间你好,这个比方还是不懂,什么是第一个 个数 和 第四个 个数 形成了 3k 的区间,能用具体例子来说明一下吗,谢谢 数列是 1,2,3,4,5 他的前缀和是 1,3,6,10,15,用这个例子说一下可以吗
前缀和和是1,3,6,10,15,对2取模的余数分别是1,1,0,0,1,那么余数相同的两个数可以形成一个2倍的区间,例如余数是1的前缀和有3个,因此有3个2倍的区间,余数是0的有两个,又因为本身余数是0已经占了一个,因此也是有3个2倍的区间,所以总共是6个
tql