1234. 倍数问题题解 (余数dp线性)——南昌理工学院

  1. 倍数问题题解 (余数dp线性)
    众所周知,小葱同学擅长计算,尤其擅长计算一个数是否是另外一个数的倍数。

但小葱只擅长两个数的情况,当有很多个数之后就会比较苦恼。

现在小葱给了你 n 个数,希望你从这 n 个数中找到三个数,使得这三个数的和是 K 的倍数,且这个和最大。

数据保证一定有解。

输入格式
第一行包括 2 个正整数 n, K。

第二行 n 个正整数,代表给定的 n 个数。

输出格式
输出一行一个整数代表所求的和。

数据范围
1≤n≤105,
1≤K≤103,
给定的 n 个数均不超过 108
输入样例:
4 3
1 2 3 4
输出样例:
9

题目意思是给你N个数从中选三个数,求这三个数之和是k的倍数的最大值,输出这个最大值就好了。我们分析完题目了,再看看数据范围1≤n≤105,1≤K≤103,便可以看出这是一道dp型问题。难点在于怎么样去dp,明显我们可以知道这道题目要利用mod k之后的余数去dp。
思考一下如果你用的不是余数去动态转移,那么时间复杂度将会很高;我们构造的dp表,应该是一个四层的表代表着第0层:没有数字的的时候 ;第1层:代表放一个数字的时候;第2 层:代表两个数字之和;第3层:代表三个数字之和;而横向的代表该位置的数则为所有mod k余的为该位置下标的数的最大值。
下面就以样例建dp模型:
4 3
1 2 3 4

这是最初还没有更新的初始化之后的表,就以图中第三层0位置为例:储存这三个数之和且mod 4之后为0的最大的数;
当第一个数进入之后就将更新加入a[1]偏移量为1%k;这都是结果表。
在这里插入图片描述
当第二个数进入之后就将更新加入a[2]偏移量为2%k,从原来位置加上a[2]%k转移到下一行的某个位置((则变为原来位置上的值加上a[2]的值)%k的位置),值则变为原来位置上的值加上a[2]的值,但是还要和转移之后位置内原来的数比较;这都是结果表。
在这里插入图片描述
当第三个数进入之后就将更新加入a[2]偏移量为2%k,从原来位置加上a[2]%k转移到下一行的某个位置((则变为原来位置上的值加上a[2]的值)%k的位置),值则变为原来位置上的值加上a[2]的值,但是还要和转移之后位置内原来的数比较;这都是结果表。
在这里插入图片描述
第四个数进来也相同;这都是结果表
在这里插入图片描述
可能这张图看起来不容易理解,为什么从6变成了7,实际上是有3变成7的,这里更新要从第三个数进来之后的结果表的位置来看,所以箭头的起始值(应该是第三个数进来之后的结果表位置上存的值来变)才能到达终止值。

开始以为这样子从头到尾跑一边就行,结果发现3*10^8时间复杂度过不了;这道题因此还要优化,而这个优化则想明白了就想对简单:就无论怎么选最大值 只和% k这位置上的三个最大值有关,所以要每个位置最多只用保存三个(%k为该位置)最大的数。
代码实现有点繁琐勿怪:

#include<bits/stdc++.h>
#include<cmath>
using namespace std;
const int N=100100;
int n,m;
int a[N],a1[N];
long long ans[1005][4],dp[1005][4];
int main() {
    cin>>n>>m;
    for(int i=1; i<=n; i++) {
        cin>>a[i];
    }
    for(int j=3; j>=0; j--)
        for(int k=0; k<=m-1; k++)
            ans[k][j]=-1,dp[k][j]=-1;//初始化ans表,和筛选数的表初始化 
    ans[0][0]=0;
    
    for(int i=1; i<=n; i++) {
        int max1=a[i],t=-1;
        for(int num=1; num<=3; num++) {
            if(max1>dp[max1%m][num]) {
                t=dp[max1%m][num];
                dp[max1%m][num]=max1; //把所有符合条件(% k这位置上的三个最大值)的数找出来 
                max1=t;
            }
        }
    }

    int sign=0;
    for(int i=1; i<=3; i++) {
        for(int j=0; j<m; j++) {
            if(dp[j][i]!=-1) {			//把所有符合条件的数转移到一个数组中 
                a1[++sign]=dp[j][i];
            }
        }
    }

    for(int i=1; i<=sign; i++) {
        for(int j=3; j>0; j--) {
            for(int k=0; k<=m-1; k++) {//更新ans表 
                if(ans[(k-a1[i]%m+m)%m][j-1]!=-1)// (k-a1[i]%m+m)%m为上一行原先的位置 
                    ans[k][j]=max((ans[(k-a1[i]%m+m)%m][j-1]+a1[i]),ans[k][j]);//表示上一行的某个位置加a1[i]变成这一行的该位置,值也更新 
            }
        }
    }
    cout<<ans[0][3]<<endl;//第三层0位置由三个数之和最大值。 
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值