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与其说是一种算法,不如说是一种思想,十分灵活,没有固定的算法模板,加油吧。
路漫漫其修远兮,吾将上下而求索。

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

被折叠的 条评论
为什么被折叠?



