暴力解法 + 优化
暴力解法就是双重循环。
- 优化点在于我们从最长开始判断可以尽早的结束。只要找到第一次整除,即可获取到结果。
所以外层循环为长度,内循环为该长度的起始点。
比如对于长度n,从索引1开始,此时就1个子串。
比如对于长度n - 1, 从索引1开始会有2个子串,是:[1, n - 1], [2, n]。
以此类推,直到外循环长度为0结束。
其上可以在第一次整除的情况结束!
- 为了便于更快得到子串的和,所以用初始数组直接记录和的贡献。
复杂度$O(n ^ 2)$
代码
long[] nums = new long[n + 1];
for(int i = 1; i <= n; i++){
nums[i] = nums[i - 1] + input.nextInt();
}
for(int j = n; j >= 1; j--){
for(int i = 0; i + j <= n; i++){
if((nums[i + j] - nums[i]) % k == 0){
out.println(j);
out.close();
return;
}
}
}
同余定理 + 状态压缩
我们其实就是在找这个等式:
sum[i] - sum[j] = t * k \space =>
sum[i]\% k - sum[j] \% k = 0 \space =>
sum[i] \% k = sum[j] \% k
sum[i]和sum[j]为前缀和,其实就是前i个值的和减去 前j个值的和,意思就是区间[j, i]的子串。
根据以上我们可以把n种状态压缩为k种,我们遍历一次数组,对于第一次取得的余数记录其所在位置,然后下一次遇到同样的,即减去之前第一次的位置,就是满足k倍的长度。**注意0要特殊处理,因为0个数的和的余数也是0,相当于直接就有了。取余为0的位置直接就是长度。
复杂度$O(n)$
代码
public static void main(String[] args){
FastScanner input = new FastScanner();
PrintWriter out = new PrintWriter(System.out);
int n = input.nextInt();
int k = input.nextInt();
int[] start = new int[k];
int sum = 0, num, ans = 0;
for(int i = 1; i <= n; i++){
num = input.nextInt();
sum = (sum + num) % k;
if(sum != 0 && start[sum] == 0 ){
start[sum] = i;
}else {
ans = Math.max(ans, i - start[sum]);
}
}
out.println(ans);
out.close();
}
拓展
那最长子序列为k的倍数的长度怎么算呢?
还是上面的思路,只不过我们不能一次遍历就解决了,我们需要对于每个数,都要考察它对于每个余数的贡献。即当前的数在前面的各种余数的基础上产生更新,类似最长上升子序列的思路。比如当前的数在j的余数上产生新的余数为k,那么对应余数k的长度就应该为上一次余数j的基础上加1的长度与上一次的余数k的长度取最大值。这样我们便可以对于每个余数更新求出当前的最大值。所以这个思路不仅可以求出余数为0的长度(也就是题目要求的最长子序列为k的倍数),还可以求出任意余数的长度。
状态转移方程如下:
dp[last][(j + num[i])\%k] = max(dp[1 -last][(j + num[i])\%k], dp[1 - last][j] + 1) \space if \space dp[1 - last][j] \neq 0
dp[last][(j + num[i])\%k] = dp[1 - last][(j + num[i])\%k] \space\space if \space dp[1 - last][j] = 0
例题
链接:https://www.nowcoder.com/acm/contest/91/L
来源:牛客网
给一个数组 a,长度为 n,若某个子序列中的和为 K 的倍数,那么这个序列被称为“K 序列”。现在要你 对数组 a 求出最长的子序列的长度,满足这个序列是 K 序列。
输入描述:
第一行为两个整数 n, K, 以空格分隔,第二行为 n 个整数,表示 a[1] ∼ a[n],1 ≤ n ≤ 105 , 1 ≤ a[i] ≤ 109 , 1 ≤ nK ≤ 107
输出描述:
输出一个整数表示最长子序列的长度 m
示例1
输入
7 5
10 3 4 2 2 9 8
输出
6
代码
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.StringTokenizer;
/**
* Created by Special on 2018/4/18 23:11
*/
public class Main {
public static void main(String[] args){
FastScanner input = new FastScanner();
PrintWriter out = new PrintWriter(System.out);
int n = input.nextInt();
int k = input.nextInt();
int[] nums = new int[n + 1];
int[][] dp = new int[2][k + 1];
for(int i = 1; i <= n; i++){
nums[i] = input.nextInt() % k;
}
dp[0][nums[1]] = 1;
int last = 0;
for(int i = 2; i <= n; i++){
last = 1 - last;
for(int j = 0; j < k; j++){
if(dp[1 - last][j] != 0){
dp[last][(j + nums[i]) % k] =
Math.max(dp[1 - last][(j + nums[i]) % k], dp[1 - last][j] + 1);
}else {
dp[last][(j + nums[i]) % k] = dp[1 - last][(j + nums[i]) % k];
}
}
}
out.println(dp[last][0]);
out.close();
}
}