题目大意:一个长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就是最少的修改个数。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | F | |
---|---|---|---|---|---|---|---|---|---|
数 | 30 | 10 | 30 | 30 | 20 | 10 | 80 | 50 | 7 |
[4,6]改成50 | 30 | 10 | 30 | 30 | 50 | 50 | 50 | 50 | 3 |
[4,6]改成30 | 30 | 10 | 30 | 30 | 30 | 30 | 30 | 50 | 3 |
直接删去[4,6] | 30 | 10 | 30 | 30 | - | - | - | 50 | 3 |
如果我们想要修改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;
}