D. Flood Fill(三种方法详解)

导读

①区间DP
②最长公共子序列
③最长回文子序列

我觉得每种方法都值得一看.

. D P \color{Red}Ⅰ.区间DP

, d p 先说一下正解,区间dp

d p [ l ] [ r ] [ 0 ] [ l , r ] a [ l ] 设dp[l][r][0]表示区间[l,r]颜色都是a[l]

d p [ l ] [ r ] [ 1 ] [ l , r ] a [ r ] dp[l][r][1]表示区间[l,r]颜色都是a[r]

1 那我们从长度为1的区间开始枚举

[ l , r ] l e n 当前枚举到的区间是[l,r]且长度为len

l e n + 1 所以可以向len+1的区间转移

d p [ l ] [ r + 1 ] [ 1 ] d p [ l ] [ r ] [ 0 / 1 ] 比如dp[l][r+1][1]可以由dp[l][r][0/1]转移而来

a [ r + 1 ] [ l , r ] 转移的时候只需要对比新加入的a[r+1]颜色是否和[l,r]颜色相同

1 如果不同就需要再花费1来变得相同

#include <bits/stdc++.h>
using namespace std;
const int maxn=5009;
inline int max(int a,int b){ return a>b?a:b; }
int n;
int a[maxn],b[maxn],top;
int dp[maxn][maxn][2],ans;
void init(){
	for(int i=1;i<=top;i++)
	for(int j=1;j<=top;j++)
	for(int q=0;q<=1;q++)
	if(i!=j)
		dp[i][j][q]=1e9;	
}
int main()
{
	cin >> n;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i] );
		if(a[i]==a[i-1])	continue;
		else	b[++top]=a[i];//变成连通块减小时间复杂度
	}
	for(int len=1;len<=top;len++)
	for(int l=1;l+len-1<=top;l++)
	{
		int r=l+len-1;
		//对于长度为len的区间,可以转移到len+1的区间 
		dp[l-1][r][0]=min(dp[l-1][r][0],dp[l][r][0]+(b[l-1]!=b[l]));//往左转移一个区间长度 
        dp[l-1][r][0]=min(dp[l-1][r][0],dp[l][r][1]+(b[l-1]!=b[r]));
 
        dp[l][r+1][1]=min(dp[l][r+1][1],dp[l][r][0]+(b[l]!=b[r+1]));//往右转移一个区间长度 
        dp[l][r+1][1]=min(dp[l][r+1][1],dp[l][r][1]+(b[r]!=b[r+1]));
	}
	cout<<min(dp[1][top][0],dp[1][top][1])<<endl;
}

. \color{Red}Ⅱ.枚举最长上升子序列

, O ( n 3 ) , O ( n 2 ) 先说一下,这个方法期望复杂度比O(n^3)低,比O(n^2)高

实际评测很容易被极限数据卡掉

枚举起始点k,那么分成了[1,k-1]和[k+1,n]两个区间

l e n 那么求一下这两个区间地最长公共子序列len即可

l e n , , 2 1 这个len就是我们能节省的花费,因为如果左右两侧相同,花费由2变成1

l e n 最后答案就是原本暴力的花费-len

#include <bits/stdc++.h>
using namespace std;
const int maxn=5009;
inline int max(int a,int b){ return a>b?a:b; }
int n;
int a[maxn],s[maxn],top;
int dp[maxn][maxn],ans;
int isok(int q,int w)
{
	//求q到1和w到top的最大公共子序列
	int ans=0,xian=max(q,n-w+1);
	for(int i=0;i<=xian;i++)	dp[1][i]=dp[i][1]=0;
	for(int i=q,x=1;i>=1;i--,x++)
	for(int j=w,y=1;j<=top;j++,y++)
	{
		if(s[i]==s[j])	dp[x][y]=dp[x-1][y-1]+1;
		else	dp[x][y]=max(dp[x-1][y],dp[x][y-1]);
		ans=max(dp[x][y],ans);
	}	
	return ans;
}
int main()
{
	cin >> n;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i] );
		if(a[i]==a[i-1])	continue;
		else	s[++top]=a[i];
	}
	for(int i=1;i+2<=top;i++)//选定i+1为起始点
			ans=max(ans, isok(i,i+2) );	
	cout<<top-ans-1; 
}

. \color{orange}Ⅲ.回文子序列

, O ( n 2 ) 这种方法是上面的进阶,复杂度是O(n^2)

, 1   2   1 其实压根不需要去枚举起始点,比如1\ 2\ 1

1 1 1 , 2 1 = 1 1和1相同所以可以节省1次花费,总花费就是2-1=1

l e n , l e n / 2 所以求出最长的回文子序列len,原花费-len/2就是答案

? 最长回文子序列怎么求?

, 把原序列倒序,然后和原序列做最长公共子序列即可

实测92ms,快到爆炸有没有!!

#include <bits/stdc++.h>
using namespace std;
const int maxn=5009;
inline int max(int a,int b){ return a>b?a:b; }
int n;
int a[maxn],s[maxn],b[maxn],top;
int dp[maxn][maxn],ans;
int main()
{
	cin >> n;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i] );
		if(a[i]==a[i-1])	continue;
		else	s[++top]=a[i];
	}
	for(int i=1;i<=top;i++)	b[i]=s[top-i+1];
	for(int i=1;i<=top;i++)
	for(int j=1;j<=top;j++)
	{
		if(b[i]==s[j])	dp[i][j]=dp[i-1][j-1]+1;
		else	dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
		ans=max(ans,dp[i][j]);
	}
	cout<<top-1-ans/2; 
}

猜你喜欢

转载自blog.csdn.net/jziwjxjd/article/details/106837209