Description
给定一个大小为 \(n \times m\) 的矩阵,要求每行只能选取不超过一半的元素,使得所有选出元素的总和是 \(k\) 的倍数,且这个总和最大。求这个最大值。\(n,m,k \le 70\)。
Solution
考虑 dp,对于每一行 \(i\),首先预处理出 \(f[i][j][l][res]\) 表示在第 \(i\) 行中,从前 \(j\) 个数中选择了 \(l\) 个数,和 \(\mod k = res\) 的最大和为多少。据此,我们可以对 \(f[i][*][*][res]\) 取 \(\max\) 得到 \(g[i][res]\),即从第 \(i\) 行中选不超过一半的数,且满足总和 \(\mod k = res\) 的限制条件时,能够达到的最大的和是多少。
设 \(h[i][res]\) 表示考虑前 \(i\) 行,从里面选取若干个数(当然数量要合法),且满足总和 \(\mod k = res\) 的限制条件时,能够达到的最大的和是多少。利用 \(g[i][res]\) 显然可以轻松计算。
#include <bits/stdc++.h>
using namespace std;
#define dbg(x) cout<<#x<<" = "<<x<<", "
#define dbgn(x) cout<<#x<<" = "<<x<<endl
const int N = 75;
int a[N][N],n,m,k;
int f[N][N][N][N],g[N][N],h[N][N];
signed main()
{
cin>>n>>m>>k;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>a[i][j];
}
}
memset(f,-1,sizeof f);
memset(g,-1,sizeof g);
memset(h,-1,sizeof h);
for(int i=1;i<=n;i++)
{
f[i][0][0][0]=0;
for(int j=1;j<=m;j++)
{
for(int r=0;r<k;r++) f[i][j][0][r]=f[i][j-1][0][r];
for(int l=1;l<=j;l++)
{
for(int r=0;r<k;r++)
{
int pos=(r-a[i][j]+100*k)%k;
f[i][j][l][r]=f[i][j-1][l][r];
if(f[i][j-1][l-1][pos]!=-1)
{
f[i][j][l][r]=max(f[i][j][l][r],f[i][j-1][l-1][pos]+a[i][j]);
}
}
}
}
}
for(int i=1;i<=n;i++)
{
g[i][0]=0;
for(int r=0;r<k;r++)
{
int ans=-1;
for(int l=0;l<=m/2;l++)
{
ans=max(ans,f[i][m][l][r]);
}
g[i][r]=ans;
}
}
h[0][0]=0;
for(int i=1;i<=n;i++)
{
for(int r=0;r<k;r++)
{
for(int d=0;d<k;d++)
{
if(h[i-1][r]!=-1 && g[i][d]!=-1)
{
h[i][(r+d)%k]=max(h[i][(r+d)%k],h[i-1][r]+g[i][d]);
}
}
}
}
cout<<h[n][0]<<endl;
return 0;
}