题目链接:https://vjudge.net/problem/Kattis-smootharray
题解:
每k个数之和都是s,所以a1+a2+...+ak=a2+a3+...+a(k+1)=s,所以a1=a(k+1)。同理,可以推出题目要求的数组每k个一循环(不过题目中好像已经直接提示了这点
因为是以k个一循环,所以数组中下标对k同余的位置最后都得变成同一个数,因此我们将这些位置按除k的余数分组来考虑,每组选出一个数字,最后使得数字之和为s
到这应该不难看出要使用背包dp的思想来解决这道题了。背包的容量是s,每组(代表的位置为ai, a(i+k), a(i+2*k)...)选一个数字,放到背包里的体积就是数字的大小,价值就是这个数字在这一组位置中出现的次数。dp[i][j]表示枚举到第i组,前面选取的数总和为j时的最大价值,dp完成后只要用n减去dp[n][s](体积要求正好等于背包的大小,也就是k)就可以得到最小操作次数。
但这样还存在一个问题,每次如果直接从[1, s]枚举要选哪个数字来进行转移,时间复杂度会达到O(k*s*s),显然会超时。
可以这样来优化,枚举选哪个数字的时候仅枚举在这组位置中出现的数字,若要选取在这组位置中没出现过的数字采用另一种办法来标记。因为如果要选取没出现过的数字,这个数字要选什么就没有约束,可以从[1, s]中任意选,我们不妨先不计入j,仅仅标记一下这个状态存在可以自由选择的数,这样在最后dp完时,对于存在可自由选择的数的状态,就不要求其体积一定正好为k(因为只要体积比k小,就可以用那个可自由选择的数凑出k
因此,在dp数组最后加一维[0/1],dp[i][j][0]表示这个状态枚举了前i组,选出的数字总和为j,不存在可自由选择得数;dp[i][j][1]表示这个状态枚举了前i组,选出的数字总和为j(注意此处,这个j仅是不可自由选择数字之和),存在至少一个可自由选择的数。
优化后,dp时一层循环枚举k组位置,一层循环选取在这组位置中出现过的数字(不超过n/k种),还有一层循环枚举之前状态的体积进行转移,复杂度为O(k*(n/k)*s)。
在dp数组最后加一维[0/1],dp[i][j][0]表示这个状态枚举了前i组,选出的数字总和为j,并且前i组选数的时候选的都是这组位置中出现过的数字,dp[i][j][1]表示这个状态枚举了前i组,选出的数字总和为j
代码:
#include<bits/stdc++.h>
using namespace std;
int num[5010], dp[5010][5010][2], n, k, s, ans;
int main()
{
memset(dp, 128, sizeof(dp));//初始化,除了最初状态的价值设为0,其余状态价值都设为负无穷大
dp[0][0][0]=0;
cin>>n>>k>>s;
for (int i=1; i<=n; i++)
cin>>num[i];
for (int i=1; i<=k; i++)
{
unordered_map<int, int> mp;
for (int j=i; j<=n; j+=k)
mp[num[j]]++;//记录这组位置中有哪些数出现过,出现了几次
for (int j=0; j<=s; j++)
{
for (auto &x: mp)
{
if (j+x.first>s) continue;
//从在这些位置里出现过的数字中选取,计入j
dp[i][j+x.first][0]=max(dp[i][j+x.first][0], dp[i-1][j][0]+x.second);
dp[i][j+x.first][1]=max(dp[i][j+x.first][1], dp[i-1][j][1]+x.second);
}
//从不在这些位置里出现的数字选取,先不计入j
dp[i][j][1]=max(dp[i][j][1], dp[i-1][j][0]);
dp[i][j][1]=max(dp[i][j][1], dp[i-1][j][1]);
}
}
for (int i=0; i<=s; i++)//对于存在可自由选择数字的状态,都可以更新答案
ans=max(ans, dp[k][i][1]);
ans=max(ans, dp[k][s][0]);//不存在可自由选择数字,体积就得正好为k
cout<<n-ans;
return 0;
}