- 倍数问题题解 (余数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;
}