Flood Fill 区间dp练习

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)
但其实要求的就是整个序列的最长奇数回文子序列。最长奇数回文子序列的中间位置就是起始点。而由于数组中所有相邻的数都是不同的,所有最长公共子序列长度必定为奇数。(若长度为偶数,那么子序列中间位置的两个相同的数由于在原序列中不能相邻,他们中间必定还夹了别的数,这样导致该子序列就不再是最长了)
所以其实最后就转化为了求最长回文子序列。最长回文子序列的求解方法:对原序列和它的逆序序列求最长公共子序列。
如果想证明为什么可以从如下两个点入手:

  1. 最长回文子序列的长度<=原序列和它的逆序序列的最长公共子序列长度
  2. 原序列和它的逆序序列的最长公共子序列必定是回文的

这里就不加以证明。
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;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值