题目链接:点击这里
题目大意:
给定一串长度为
n
n
n 的数字,将其分割为
k
k
k 段区间。各段区间数字相加模
p
p
p 后求和,求其最大值
题目分析:
容易想到一种
O
(
n
2
k
)
O(n^2k)
O(n2k) 的区间
d
p
dp
dp 做法:定义状态
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 表示用前
i
i
i 个数分了
k
k
k 段的最大值
转移为
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
]
[
j
]
,
d
p
[
o
]
[
j
−
1
]
+
(
s
u
m
[
i
]
−
s
u
m
[
o
]
)
%
p
)
dp[i][j]=max(dp[i][j],dp[o][j-1]+(sum[i]-sum[o])\%p)
dp[i][j]=max(dp[i][j],dp[o][j−1]+(sum[i]−sum[o])%p)
考虑优化,发现
p
≤
100
p\le 100
p≤100 ,范围很小,所以尝试从此处入手, 枚举模
p
p
p 后的余数
状态
d
p
[
s
u
m
[
i
]
%
p
]
[
j
]
dp[sum[i]\%p][j]
dp[sum[i]%p][j]含义变为了前
i
i
i 个数模
p
p
p 结果为
s
u
m
[
i
]
%
p
sum[i]\%p
sum[i]%p 分为了
j
j
j 组的最大值,最后答案就是
d
p
[
s
u
m
[
n
]
%
p
[
j
]
dp[sum[n]\%p[j]
dp[sum[n]%p[j]
其转移方程为
d
p
[
s
u
m
[
i
]
]
[
j
]
=
m
a
x
(
d
p
[
s
u
m
[
i
]
[
j
]
,
d
p
[
o
]
[
j
−
1
]
+
(
s
u
m
[
i
]
%
p
−
o
)
%
p
)
dp[sum[i]][j] = max(dp[sum[i][j],dp[o][j-1]+(sum[i]\%p-o)\%p)
dp[sum[i]][j]=max(dp[sum[i][j],dp[o][j−1]+(sum[i]%p−o)%p)
这样时间复杂度就变成了
O
(
n
k
p
)
O(nkp)
O(nkp)
具体细节见代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<stack>
#define ll long long
#define inf 0x3f3f3f3f
#define Inf 0x3f3f3f3f3f3f3f3f
#define int ll
using namespace std;
int read()
{
int res = 0,flag = 1;
char ch = getchar();
while(ch<'0' || ch>'9')
{
if(ch == '-') flag = -1;
ch = getchar();
}
while(ch>='0' && ch<='9')
{
res = (res<<3)+(res<<1)+(ch^48);//res*10+ch-'0';
ch = getchar();
}
return res*flag;
}
const int maxn = 1e5+5;
const int mod = 1e9+7;
const double pi = acos(-1);
const double eps = 1e-8;
int n,k,p,a[maxn],sum[maxn],dp[maxn][55];
signed main()
{
n = read(),k = read(),p = read();
for(int i = 1;i <= n;i++) a[i] = read()%p,sum[i] = (sum[i-1]+a[i])%p;
memset(dp,-127,sizeof(dp));
dp[0][0] = 0;
for(int i = 1;i <= n;i++)
for(int j = 1;j <= k;j++)
for(int o = 0;o < p;o++)
dp[sum[i]][j] = max(dp[sum[i]][j],dp[o][j-1]+(sum[i]-o+p)%p);
cout<<dp[sum[n]][k]<<endl;
return 0;
}