cf1114 D. Flood Fill(dp,区间dp)
题意:
一维数组中如果相邻的两个数字相等,则称它们属于同一个连通块。任选一个连通块(这不计步数)开始操作,每次操作可把当前连通块中的所有数同时变成另一个数,问至少几次操作后可使数组中的所有数相等。
思路:
法一:最长公共子序列/最长回文子序列
如果遇到形如 \(bbbaaabb\) 的序列,则从 \(a\) 所在的连通块开始只需要操作一次。总共节省的次数是起点左边的数组与起点右边的数组的最长公共子序列。但枚举起点做n次最长公共子序列会超时。
改为求整个数组的最长回文子序列的长度len,答案就是n-1-len/2。(原本需要n-1次)
求最长回文子序列就是原数组和倒序数组求一次最长公共子序列!
#include <bits/stdc++.h>
using namespace std;
const int N = 5005;
int n, a[N], b[N], f[N][N];
signed main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
int m = 0; for(int i = 1; i <= n; i++) if(a[i] != a[i-1]) a[++m] = a[i];
n = m; //合并相邻的相等数
memcpy(b, a, sizeof a), reverse(b + 1, b + 1 + n);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
{
f[i][j] = max(f[i-1][j], f[i][j-1]);
if(a[i] == b[j]) f[i][j] = max(f[i][j], f[i-1][j-1] + 1);
}
cout << n - 1 - f[n][n]/2;
return 0;
}
法二:区间dp
\(f(l,r,0)\) 表示区间 \([l,r]\) 里的数全部等于 \(a[l]\) 的花费,1表示全部等于 \(a[r]\) 。
要注意,只能把 \(a[l,r-1]\) 变成 \(a[r]\) 从而扩展到 \([l,r]\),或者把 \(a[l+1,r]\) 变成 \(a[l]\) 从而扩展到 \([l,r]\)。也就是只能 “区间并到点” ,不能 “区间并区间” 或者 “点并到区间” 。所以不用枚举分界点。
for(int len = 2; len <= n; len++)
for(int l = 1; l + len - 1 <= n; l++)
{
int r = l + len - 1;
f[l][r][0] = min(f[l+1][r][0] + 1, f[l+1][r][1] + (a[l] != a[r]));
f[l][r][1] = min(f[l][r-1][1] + 1, f[l][r-1][0] + (a[l] != a[r]));
}
cout << min(f[1][n][0], f[1][n][1]);

浙公网安备 33010602011771号