UVA1625 Color Length(线性DP

这篇博客介绍了如何解决UVA1625问题,该问题与最长公共子序列(LCS)类似,采用动态规划(DP)策略。文章详细阐述了状态转移方程,并提出预处理‘cnt’矩阵以优化时间复杂度,讨论了DP问题的关键要素和解题思维。

UVA1625 Color Length (线性DP)

类似于LCS问题,可以用dp[i][j]来表示已选择序列1前i项和序列2前j项的最小目标函数值L( c )。显然,dp[0][0]=0;

因为在选序列1第i个项的前提是前i-1个项必须被选择,序列2同理。
所以dp[i][j]仅可能从dp[i-1][j]和dp[i][j-1]两种状态转移而来。

对目标函数的处理,借鉴紫书上的方法:当把一个元素放到最终序列的时候,递增“已经出现但还未结束”的颜色的L( c )值。

最初的想法是在每次状态转移的时候计算目标函数增加值,对26个字母逐一判断是否出现在最终序列且还未选择完,结果TLE。

参考了他人的想法,先预处理好已选了序列1前i个,序列2前j个时,需要进行递增的数目,用cnt[i][j]表示。

显然,当i!=0且j!=0是,cnt[i][j]可以由cnt[i-1][j]或者cnt[i][j-1]得到。
例如,从cnt[i-1][j]递推,对序列1的第i项进行判断.

如果它是第一次出现在最终序列内且后续还会出现,则cnt[i][j]=cnt[i-1][j]+1;

如果它是最后一次出现在最终序列里,则cnt[i][j]=cnt[i-1][j]-1;这样的判别方法,同时也考虑到只出现一次的颜色。

显然,当i=0时,cnt[i][j]只能从cnt[i][j-1]得来;当j=0时,cnt[i][j]只能从cnt[i-1][j]得来。

我们可以将元素的start值初始化为INF,把元素的end值初始化为-1或0,简化判断过程。

所以,通过上述讨论,可得状态转移方程
1、dp[i][j]=dp[i-1][j]+cnt[i-1][j] (j=0)
2、dp[i][j]=dp[i][j-1]+cnt[i][j-1] (i=0)
3、dp[i][j]=min(dp[i-1][j]+cnt[i-1][j],dp[i][j-1]+cnt[i][j-1] ) (i!=0且j!=0)

预处理的时间复杂度O(nm),dp的时间复杂度O(nm)

#include<bits/stdc++.h>
using namespace std;
const int max_n=5e3+5;
const int INF=0x3f3f3f3f;
string s1,s2;
int n,m;
int dp[max_n][max_n];
int cnt[max_n][max_n];
int start_1[26];
int end_1[26];
int start_2[26];
int end_2[26];
void solve(void)
{
	memset(start_1,0x3f,sizeof(start_1));
	memset(start_2,0x3f,sizeof(start_2));
	memset(end_1,-1,sizeof(end_1));
	memset(end_2,-1,sizeof(end_2));
	for(int i=1;i<=n;i++)
	{
		int x=s1[i-1]-'A';
		if(start_1[x]==INF)start_1[x]=i;
			end_1[x]=i;
	}
	for(int i=1;i<=m;i++)
	{
		int x=s2[i-1]-'A';
		if(start_2[x]==INF)start_2[x]=i;
			end_2[x]=i;
	}
	for(int i=0;i<=n;i++)
	for(int j=0;j<=m;j++)
	{
		if(i)
		{
			cnt[i][j]=cnt[i-1][j];
			int x=s1[i-1]-'A';
			if(start_1[x]==i&&start_2[x]>j)cnt[i][j]++;//x第一次出现在已选序列里 
			if(end_1[x]==i&&end_2[x]<=j)cnt[i][j]--;//x全部出现在右岸序列里 
		}
		else if(j)
		{
			cnt[i][j]=cnt[i][j-1];
			int x=s2[j-1]-'A';
			if(start_2[x]==j&&start_1[x]>i)cnt[i][j]++;
			if(end_2[x]==j&&end_1[x]<=i)cnt[i][j]--;
		}
	}
	dp[0][0]=0; 
	for(int i=0;i<=n;i++)
	for(int j=0;j<=m;j++)
	{
		if(i==0&&j==0)continue;
		if(i==0)dp[i][j]=dp[i][j-1]+cnt[i][j-1];
		else if(j==0)dp[i][j]=dp[i-1][j]+cnt[i-1][j];
		else dp[i][j]=min(dp[i][j-1]+cnt[i][j-1],dp[i-1][j]+cnt[i-1][j]);
	}
	cout<<dp[n][m]<<endl;
}
int main(void)
{
//	freopen("out.txt","w",stdout);
	int t;
	cin>>t;
	getchar();
	while(t--)
	{
		getline(cin,s1);
		getline(cin,s2);
		n=s1.length();
		m=s2.length();
		solve();
	}
//	fclose(stdout);
}
//2
//AAABBCY
//ABBBCDEEY
//GBBY
//YRRGB

DP问题的关键,
1、找到合适的状态,这个状态是明显的且易于转移的。
2、找到状态转移方程,弄清状态所依赖的状态。
3、恰当地对数据预处理能有效降低时间复杂度和简化代码流程。
4、dp问题变化无穷,dp与其说是一种算法,不如说是一种思想,十分灵活,没有固定的算法模板,加油吧。
路漫漫其修远兮,吾将上下而求索。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值