经典的带余数DP转移。
看到要求最后和整除K,就想到DP多开一维状态表示到当前状态下选的数和模K为多少。
然后要求每行最多选m/2个,那么开一维状态表示每行选了多少数。
然后再开两维表示选到第i行,第j列。
总共四维DP,O(n^4) 滚动数组一下可以优化两维,这里我只优化了一维。
具体看代码就行。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define re register
#define ls (o<<1)
#define rs (o<<1|1)
//#define m (l+r)/2
#define pb push_back
typedef pair<int,int> pii;
const double PI= acos(-1.0);
const int M = 1e5+7;
/*
int head[M],cnt=1;
void init(int n){cnt=1;for(int i=0;i<=n;i++)head[i]=0;}
struct EDGE{int to,nxt,w;}ee[M*2];
void add(int x,int y,int w){ee[++cnt].nxt=head[x],ee[cnt].w=w,ee[cnt].to=y,head[x]=cnt;}
*/
int dp[77][77][77];//处理到第i行,已经选了j个数,sum%K余数为k,的最大值是多少
int a[77][77];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int n,m,K;
cin>>n>>m>>K;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a[i][j];
}
}
memset(dp,-0x3f,sizeof(dp));
dp[0][0][0]=0;
for(int i=1;i<=n;i++){//i行
for(int j=1;j<=m;j++){//j列
if(j==1){
for(int k=0;k<=m/2;k++)
for(int l=0;l<K;l++)
dp[i][0][l]=max(dp[i][0][l],dp[i-1][k][l]);
//cout<<i<<" "<<k<<" "<<l<<" "<<dp[i][k][l]<<endl;;
}
for(int k=m/2-1;k>=0;k--){//当前行已经选了k个数
for(int l=0;l<K;l++){//选这个数之前余为l
//选当前数
dp[i][k+1][(l+a[i][j])%K]=max(dp[i][k+1][(l+a[i][j])%K],dp[i][k][l]+a[i][j]);
// cout<<i<<" "<<k+1<<" "<<(l+a[i][j])%K<<" = "<<dp[i][k+1][(l+a[i][j])%K]<<endl;
}
}
}
}
int ans=0;
for(int i=0;i<=m/2;i++)
ans=max(ans,dp[n][i][0]);
cout<<ans<<endl;
return 0;
}