题意:给定你一个n*m的矩阵,然后每一行中可以取一个或者多个元素,(每行最多m/2个),然后问你这些数的和在能整除k的情况下的最大的可能值是多少?
数据量不大的情况下,看到最大的这种限制条件,很容易想到dp,但我又不会,剽窃他人智慧。
因为这道题是在一个二维的矩阵中操作,并且还附加了一个特定的取模值的条件,所以我们开一个四维空间的dp数组,(2e7左右,极限) 。
dp[ i ][ j ][ l ][ r ] 代表到第i,j位置取了l个数的时候,这l个数的和对k取模为r的情况下,最大值为多少。
我们先考虑从j-1状态推得j,我们要找到的一个j-1状态下的模x使得(x + a[i][j]%k)%k == r,r也就是第j个位置当前的余数,所以这样推我们还要枚举找到x。
然后我们就考虑用j去推j+1,会有两种情况:
1.我们不取第j+1位置上的这个数,此时dp[ i ][ j + 1][ l ][ r ] = max(dp[ i ][ j + 1][ l ][ r ],dp[ i ][ j ][ l - 1][ r ]);
2.我们取第j+1个位置时,dp[ i ][ j + 1][ l ][ (r + mp[ i ][j + 1])%k ] = max(dp[ i ][ j + 1][ l ][ (r + mp[ i ][j + 1])%k ],dp[ i ][ j ][ l - 1][ r ] + mp[i][j+1];
还有就是我们也要考虑层之间的关系,因为当前这一层的状态是由上一层的状态退出的,所以第i+1层的初始状态还应该由第i层更新一下:
dp[i+1][0][0][r] = max(dp[i][j][l][r],dp[i+1][0][0][r]);
然后我们答案应该就是第n+1层的初始状态dp[n+1][0][0][0]。
AC代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 77;
const int INF = 0x3f3f3f3f;
int mp[MAXN][MAXN];
int dp[MAXN][MAXN][MAXN][MAXN];
//dp[i][j][l][r]第i行第j位选择了l个数中且这l个数对k的余数为r的和的最大值为多少
//每一行中最多选择 m/2 个元素
int n,m,k;
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i = 1;i <= n;i ++){
for(int j = 1;j <= m;j ++){
scanf("%d",&mp[i][j]);
}
}
for(int i = 0;i <= n;i ++)
for(int j = 0;j <= m;j ++)
for(int l = 0;l <= m;l ++)//选几个数
for(int r = 0;r < k;r ++)
dp[i][j][l][r] = -INF;
//memset(dp,-INF,sizeof(dp));
dp[1][0][0][0] = 0;
for(int i = 1;i <= n;i ++){
for(int j = 0;j < m;j ++){//这里是递推的j+1 从0开始 到m-1结束
for(int l = 0;l <= min(j+1,m/2);l ++){
for(int r = 0;r < k;r ++){
dp[i][j+1][l][r] = max(dp[i][j+1][l][r],dp[i][j][l][r]);//不选择第j+1个数 直接转移过来
if(l && dp[i][j][l-1][r] != -INF)//如果长度不为0并且前面的数选过了 就选择第j个数
dp[i][j+1][l][(r + mp[i][j+1])%k] = max(dp[i][j+1][l][(r + mp[i][j+1])%k],dp[i][j][l-1][r] + mp[i][j+1]);
}
for(int r = 0;r < k;r ++)
dp[i+1][0][0][r] = max(dp[i+1][0][0][r],dp[i][j+1][l][r]);//更新当前位置的时候 同时实时更新到下一行
}
}
}
printf("%d\n",dp[n+1][0][0][0]);//第n+1行代表的就是 第n行统计过的答案
return 0;
}