Smooth Array 分组背包

题目链接: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;
}

猜你喜欢

转载自www.cnblogs.com/opppppppp/p/12814728.html