Codeforces Round #538 (Div. 2) D - Flood Fill
题目链接:https://codeforces.com/contest/1114/problem/D
题目大意
给予一个长度为n的整数串,起初选取某一个位置,然后某一轮可以将与该位置连通的(连通:相邻且数字相同,连通具有传递性)所有数字全部改为另一个数字。给定整数串,问最少需要多少轮才能使所有数字变为相同。起始位置可以任选。
做法
首先,很容易看出来,我们可以先对输入的数组进行压缩,即将相邻的相同的数字压缩成一个数字。(这样问题和原来是等价的)代码如下:
int x,cnt=0;
for(int i=0;i<n;i++)
{
cin>>x;
if(x==arr[cnt])continue;//去掉相邻重复元素
else arr[++cnt]=x;
}
n=cnt
其实就是一个特征提取的过程,去掉无用的信息。
最长回文子序列
在不同的数字之间都加一个隔板,那么每次改变数字最多去掉两个隔板,那就是连通块两边数字相同的时候。那么我们就保证两边相同数字尽量多就好了,其实就是对于起始位置而言,对左右两侧求一个最长公共子序列。但是这样的话需要枚举所有的起始位置,时间复杂度接近
O
(
n
3
)
O\left( n^{3}\right)
O(n3)。
但其实要求的就是整个序列的最长奇数回文子序列。最长奇数回文子序列的中间位置就是起始点。而由于数组中所有相邻的数都是不同的,所有最长公共子序列长度必定为奇数。(若长度为偶数,那么子序列中间位置的两个相同的数由于在原序列中不能相邻,他们中间必定还夹了别的数,这样导致该子序列就不再是最长了)
所以其实最后就转化为了求最长回文子序列。最长回文子序列的求解方法:对原序列和它的逆序序列求最长公共子序列。
如果想证明为什么可以从如下两个点入手:
- 最长回文子序列的长度<=原序列和它的逆序序列的最长公共子序列长度
- 原序列和它的逆序序列的最长公共子序列必定是回文的
这里就不加以证明。
AC代码:
#include<iostream>
using namespace std;
const int MAX_N=5e3+5;
int arr[MAX_N];
int dp[MAX_N][MAX_N];
int main()
{
int n;
cin>>n;
int x,cnt=0;
for(int i=0;i<n;i++)
{
cin>>x;
if(x==arr[cnt])continue;//去掉相邻重复元素
else arr[++cnt]=x;
}
n=cnt;
for(int i=1;i<=n;i++)//一个LCS算法
{
for(int j=1;j<=n;j++)
{
if(arr[i]==arr[n-j+1])
dp[i][j]=dp[i-1][j-1]+1;
else
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
int len=dp[n][n]/2;//由于元素均不同,长度必定是奇数
cout<<n-len-1<<endl;
return 0;
}
区间dp
区间dp的做法才是这道题的正规做法。一样先将相邻的相同数字压缩。
定义dp[i][j]为[i,j]区间变成相同颜色的最小步数,那么可以根据我们在改变数字时的方式来找状态转移方程。在改变数字时,数字相同的区间的拓展要么就是往左拓展一位,要么就是往右拓展一位,要么就是区间两端新数值相同的情况下,一次性左右均拓展一位。
那么状态转移便是这样了:
dp[i][j]=min(dp[i+1][j],dp[i][j-1])+1;
if(arr[i]==arr[j])dp[i][j]=min(dp[i][j],dp[i+1][j-1]+1);
AC代码:
#include<iostream>
using namespace std;
const int MAX_N=5e3+5;
int dp[MAX_N][MAX_N];
int arr[MAX_N];
int main()
{
int n;
cin>>n;
int x,cnt=0;
for(int i=0;i<n;i++)
{
cin>>x;
if(x==arr[cnt])continue;//去掉相邻重复元素
else arr[++cnt]=x;
}
n=cnt;
for(int len=2;len<=n;len++)//区间长度
{
for(int i=1;i+len-1<=n;i++)//区间起点
{
int j=i+len-1;//区间终点
//区间划分
dp[i][j]=min(dp[i+1][j],dp[i][j-1])+1;
if(arr[i]==arr[j])dp[i][j]=min(dp[i][j],dp[i+1][j-1]+1);
}
}
cout<<dp[1][n];
return 0;
}