cf1479 B2. Painting the Array II
題意:
定義一個數組的價值為它的段數:每段連續的相同數字只保留一個,其他刪除。最後剩下的陣列的長度就是段數。
把給定陣列分成兩個子序列(順序不變,每個元素一定屬於某個子序列),求兩個子序列的價值和的最小值。
\(1\le a_i\le n\)
思路:
我是官方題解復讀機。。。
法一:dp
如果把某數字 \(x\) 放入某組,那肯定要把它後面連續的 \(x\) 也放進這組。因此可以先處理一下原陣列,使其沒有相鄰的相同數。
\(f(i)\) 表示處理到 \(i\),且 \(a_i\) 和 \(a_{i-1}\) 不在同一組的最小价值。
怎麼計算答案?假設 \(i\) 是最後一個 “\(a_i\) 和 \(a_{i-1}\)
怎麼dp?首先初值 \(f(0) = 0,f(1) = 1\)
假設 \(1\le j < i\) 是 \(i\) 之前的最後一個 “\(a_j\) 和 \(a_{j-1}\) 不在同一組” 的位置,也就是說 \(a_j,\cdots ,a_{i-1}\) 在 同一組,\(a_{j-1}\) 和 \(a_i\) 在另一組。那麼 $ f(i) = \min\limits_{1 \leq j < i} { f(j) + (i-1-j) + [a_{j-1} \neq a_i] }$
上述轉移是 \(n^2\) 的。怎麼優化?
設 \(g(j)=f(j) + (i-1-j) + [a_{j-1} \neq a_i]\),則 \(f(i) = \max\limits_{1\leq j < i}\{g(j)\}\)。然後有兩個結論:
- \(a_i \neq a_{j-1}\) 時,只需考慮 \(g(i-1)\)
證明:$g(j) =f(j)+(i-1-j)+1 =f(j)-j+i $
而 \(f(i)\le g(j)\),所以 \(f(i)-i\le f(j)-j\),即 \(f(i)-i\) 單調不增
所以 \(g(j)=f(j)-j+i \ge f(i-1)-(i-1)+i =f(i-1)+1>f(i-1).\Box\)
- \(a_i=a_{j-1}\) 時,只需考慮最大的那個 \(j\)
證明:若 \(k<j,a_i=a_{j-1}=a_{k-1}\),那麼 \(g(k)=f(k)+i-1-k \ge f(j)-j+i-1=g(j).\Box\)
綜上,只有兩種情況。
int a[N], f[N], las[N]; //某值上次出現的位置
int g(int i, int j) {
return f[j] + i - 1 - j + (a[j-1] != a[i]);
}
void sol() {
int n, m = 0; cin >> n;
for(int i = 1; i <= n; i++) {//讀入時去掉相鄰的重複值
cin >> a[i]; if(a[m] != a[i]) a[++m] = a[i];
}
f[1] = 1;
memset(las, -1, sizeof las); las[a[1]] = 1;
int ans = m;
for(int i = 2; i <= m; i++)
f[i] = min(g(i,i-1), g(i,las[a[i]]+1)),
las[a[i]] = i, ans = min(ans, f[i] + m - i);
cout << ans;
}