Kick Start 2019 F Flattening

链接Flattening

题目大意:一个长N的正整数数组A,2 ≤ N ≤ 100,第1个元素A[0],以此类推。设它的F值为相邻两个数不相等的对数,即使得A[i]≠A[i+1],且0 ≤ i < N-1的编号i的个数。现在可以修改各个元素为任意值,求最少的修改个数,使得F值≤K,0 ≤ K ≤ N。

求区间的最优值,考虑使用dp。对于任意修改的方案,设连续的被修改的元素组成一个块,可以把它们的值改成块左边未被修改的元素的值(或者右边)而不影响最优值。这样处理后A的F值与将块直接删除剩下元素的F值相同。所以被修改的元素可以视为被直接删除,问题也就变为,求使得F值≤K的最长子序列的长度L,拿N-L就是最少的修改个数。

01234567F
30103030201080507
[4,6]改成5030103030505050503
[4,6]改成3030103030303030503
直接删去[4,6]30103030---503

如果我们想要修改A[4,6],那最好把它们都改为30或50。不妨设改为30,这样A的F值为3,如果我们把A[4,6]删去,剩下的序列F值同样是3

现在开始dp。状态f(i, k)表示,A[0,i]里满足F值≤k的最长子序列的长度,我们需要的结果是N - f(N-1, K)。

定义g(i, j)为A[i,j]里出现最多次的值的频率,即最大频率

边界条件:f(0, k) = 1,f(i, 0) = g(0, i)

状态转移:当i > 0, k > 0, f(i, k) = max(f(j-1, k-1) + g(j, i)) for all 1 ≤ j ≤ i

解释一下这个方程。对于一个序列,把连续的相等的元素称为块,如果它的F是k,那它有k+1个块。比如10 20 20 30 10,F=3,4个块。当取到f(i, k)时,设选出的子序列的F=m,1 ≤ m ≤ k,分成前m块和最后1块两部分,设最后1块最左边的元素为A[j],那最后1块里一定都是A[j,i]里频率最高的值,而前m块的总长会是f(j-1,k-1)。注意这里不考虑m=0的情况,因为g(0,i) ≤ 1 + g(1,i) = f(0,k-1)

时间复杂度O(N2K)

#include <iostream>
#include <unordered_map>
#include <algorithm>

using namespace std;
const int MAXN = 100;
const int MAXK = 100;
int secs[MAXN]; // sections
int tbl[MAXN][MAXK+1];
// 可以假设rebuild的连续块会改为同样高度,等价于被删除

class Solution {
public:
    // [0,idx]最长子序列的长度,使得Ai!=Ai+1个数<=k
    // idx >= 0, k >= 0
    int dp(int idx, int k) {
        int& res = tbl[idx][k];
        if(res > 0) return res;
        unordered_map<int, int> cnt;
        if(idx == 0) {
            res = 1;
        } else if(k == 0) {
            for(int i = idx; i >= 0; i--) {
                res = max(res, ++cnt[secs[i]]);
            }
        } else {
            int maxf = 0;
            for(int i = idx; i > 0; i--) {
                maxf = max(maxf, ++cnt[secs[i]]);
                res = max(res, dp(i-1, k-1) + maxf);
            }
        }
        return res;
    }
    void solve() {
        int N, K;
        cin >> N >> K;
        for(int j = 0; j < N; j++) {
            cin >> secs[j];
        }
        for(int j = 0; j < N; j++) {
            for(int k = 0; k < K+1; k++) {
                tbl[j][k] = 0;
            }
        }
        cout << N - dp(N-1, K) << endl;
    }
};

int main() {
    int T;
    cin >> T;
    auto sln = new Solution();
    for (int i = 1; i <= T; i++)
    {
        cout << "Case #" << i << ": ";
        sln->solve();
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值