最长子串和为k的倍数的长度和最长子序列和为k的倍数的长度

暴力解法 + 优化

暴力解法就是双重循环。
- 优化点在于我们从最长开始判断可以尽早的结束。只要找到第一次整除,即可获取到结果。
所以外层循环为长度,内循环为该长度的起始点。
比如对于长度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();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值