ICPC2020昆明 Cities
题目简述
\(\qquad\)给定一串数字,对于一串连续的数字,可以将它们染色成任意数字,问最少要多少次才能把这串数字全部染成同种颜色。
思路解析
\(\qquad\)我们可以对题目进行一下转化:所有数字全部染成同种颜色意味着相邻异色数对的数量为 \(0\),那我们每次对整段的区间染色,只有以下两种情况:
1 ->
......... a b b b b a .............
\(\qquad\)当这个区间的左右端点再向外扩张 \(1\) 位的时候,如果左右的数字相同,那么将 \(bbbb\) 这一段染色全部染成 \(a\) 将会减少 \(2\) 对异色对(分别是左边的 \((a, b)\) 和右边的 \((b, a)\)),我们称呼这种情况下的 \(bbbb\) 这段为被夹着的区间
。
2 ->
......... a b b b b c .............
\(\qquad\)当这个区间的左右端点再向外扩张 \(1\) 位的时候,如果左右的数字不同,那么将 \(bbbb\) 这一段染色全部染成 \(a\) 将会减少 \(1\) 对异色对,染成 \(c\) 也同理。
\(\qquad\) 接下来我们考虑一下极端的情况,也就是整个区间所有数字都不同,这种情况显然有n-1
个异色对,由于没有同色的区间,所以一次只能消除 \(1\) 对,需要 \(n - 1\) 次操作。
\(\qquad\)然后我们可以统计被夹着的区间
总数,然后用极端情况减去它就可以了
状态表示
解释:这样是为了选择最佳的颜色来染色。
状态转移
对于 \([l,r]\) 间没有能够与 \(l\) 的颜色配对的,直接跳过, \(f[l][r] = f[l+1][r]\)
对于有能与 \(l\) 的颜色配对的,找出所有的配对点(设为 \(m\) ),并分成三部分:
\(\qquad1.\) \([l,m]\) 之间有 \(1\) 个被夹住的区间
\(\qquad2.\) \([l+1,m-1]\),继承 \(l\) 和 \(m\) 之间的答案
\(\qquad3.\) \([m, r]\),后半部分的答案,值得一提的是应该用 \(m\) 而非 \(m + 1\) 是因为 \(m\) 点可以二次利用,同时夹住两个区间。
解题流程
- 去重+处理出每个数下一个相等的数的下标,这里可以采用倒序处理的方式,具体如下。
for (int i = n; i; i -- ) {
nxt[i] = pos[a[i]];
pos[a[i]] = i;
}
pos保存的是每个数最后出现的下标
2. 开始DP,这里采用记搜
3. 输出答案
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 5010;
int f[N][N], a[N], n, T;
int nxt[N], pos[N];
int dp(int l, int r) {
int &v = f[l][r];
if (~v) return v;
if (l >= r) return v = 0;
v = dp(l + 1, r);
int x = nxt[l];
while (x <= r) {
v = max(v, dp(l + 1, x - 1) + 1 + dp(x, r));
x = nxt[x];
}
return v;
}
int main() {
scanf("%d", &T);
while (T -- ) {
scanf("%d", &n);
for (int i = 1; i <= n; i ++ ) pos[i] = n + 1;
for (int i = 1; i <= n; i ++ ) {
scanf("%d", &a[i]);
if (a[i] == a[i - 1]) -- i, -- n;
}
for (int i = n; i; i -- ) {
nxt[i] = pos[a[i]];
pos[a[i]] = i;
}
for (int i = 1; i <= n; i ++ )
for (int j = i; j <= n; j ++ ) f[i][j] = 0;
memset(f, -1, sizeof f);
printf("%d\n", n - 1 - dp(1, n));
}
return 0;
}