洛谷P4170涂色 + HDU2476:String painter(区间DP)

洛谷P4170涂色 + HDU2476:String painter(区间DP)

两题有相似之处,HDU的是洛谷P4170的变形题

洛谷P4170

https://www.luogu.com.cn/problem/P4170

HDU2476

http://acm.hdu.edu.cn/showproblem.php?pid=2476


洛谷P4170

题意

给定一个目标字符串,每次操作可以选定某个连续区段将其均改成同一个字符(一开始均为空),问要至少要进行几次才能染成目标字符串,字符串长度小于50

思路

采用区间DP,那么我们如何建立状态转移关系呢?

在确定具体的关系之前,我们应该先思考一下进行贪心的方案

比如说,我们现在要染

ABAA

它需要几步嘞?显然两步,即

0000 -> AAAA -> ABAA

我们在处理连续且相同的前后缀时,优先染他们

这种方案不会影响我们接下来对中间区段的染色

并且它能为我们节省不必要的一步!!!

有了这个贪心思想,我们可以观察一下我们某个区段的情况了,

如下图,我们假设现在研究:[j,i]这个区段的情况

那么为了应用上面的贪心方案,我们实际上是要研究[j+1,i] 与 最左端 j 的关系

假设红色线表示分割线,那么它表示说左边部分和右边部分的染色策略相互独立

上面提供了三种分割的方案

1)这个区段啥也不切

2)这个区段切的无意义

3)这个区段切完后,左半边的最右端恰好与 j 相同

第二种分割方式显然劣于第一种

第三种情况中,我们就很好的将上面的贪心方案应用了起来,

所以我们列状态转移只要考虑第一、三种情况即可

最优染色的状态转移关系如下

d p [ l ] [ r ] = m i n { 1 + d p [ l + 1 ] [ r ] , m i n { d p [ l ] [ k ] + d p [ k + 1 ] [ r ] s [ l ] = s [ k ] } }

最后要小心一下区段左右端l,r遍历的顺序即可

AC代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
using namespace std;
#define INF 0x3f3f3f3f
#define maxn 105
#define minn -105
#define ll long long int
#define ull unsigned long long int
#define uint unsigned int
inline int read()
{
    int ans=0;
    char last=' ',ch=getchar();
    while(ch<'0'|ch>'9')last=ch,ch=getchar();
    while(ch>='0'&&ch<='9')ans=ans*10+ch-'0',ch=getchar();
    if(last=='-')ans=-ans;
    return ans;
}
string s2;
int dp[maxn][maxn],ans[maxn];
void solve()
{
    memset(dp,0,sizeof(dp));
    memset(ans,0,sizeof(ans));
    int len=s2.size();
    for(int i=0;i<len;i++)
    {
        for(int j=i;j>=0;j--)
        {
            dp[j][i]=dp[j+1][i]+1;
            for(int k=j+1;k<=i;k++)
            {
                if(s2[j]==s2[k])
                    dp[j][i]=min(dp[j][i],dp[j+1][k]+dp[k+1][i]);
            }
        }
    }
    cout<<dp[0][len-1]<<endl;
}


int main()
{
    cin>>s2;
    solve();
    return 0;
}

HDU2476

题意

给你两个字符串,每次操作你可以选定某个连续区段将其均改成同一个字符,问至少多少次能够让s1到s2,字符串长度小于100

思路

这是一道区域赛的题

这道题与上一道大相径庭

唯一区别就是它初始是有原字符串的

那么与上面一题处理起来基本一样,要优先看看如何染色是最优的

处理完上面的东西后,我们现在就知道了某个区段染色的最佳方案

接下来我们要看的是,如何利用原有的字符串将来减少染色次数

我们想如果某个区段最右端颜色已经一样了,那么我们就没有染色的意义了

基于此思想,我们就可以开一个数组ans[i],记录 [0,i] 这个区段的最优值

如果i+1颜色不用变了,有ans[i+1] = ans[i]

否则,i+1的颜色肯定要改变,那么它肯定要归属于某种最优区段的划分方案中,

即通过枚举划分点,从前继状态 ans[k] + dp[k][i+1] 找最优即可

AC代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
using namespace std;
#define INF 0x3f3f3f3f
#define maxn 105
#define minn -105
#define ll long long int
#define ull unsigned long long int
#define uint unsigned int
inline int read()
{
    int ans=0;
    char last=' ',ch=getchar();
    while(ch<'0'|ch>'9')last=ch,ch=getchar();
    while(ch>='0'&&ch<='9')ans=ans*10+ch-'0',ch=getchar();
    if(last=='-')ans=-ans;
    return ans;
}
string s1,s2;
int dp[maxn][maxn],ans[maxn];
void solve()
{
	memset(dp,0,sizeof(dp));
    memset(ans,0,sizeof(ans));
    int len=s1.size();
    for(int i=0;i<len;i++)
    {
        for(int j=i;j>=0;j--)
        {
            dp[j][i]=dp[j+1][i]+1;
            for(int k=j+1;k<=i;k++)
            {
            if(s2[j]==s2[k])
                dp[j][i]=min(dp[j][i],dp[j+1][k]+dp[k+1][i]);
            }
        }
    }
    for(int i=0;i<len;i++)
        ans[i]=dp[0][i];
    for(int i=0;i<len;i++)
    {
        if(s1[i]==s2[i])
        ans[i]=ans[i-1];
        else
        {
            for(int j=0;j<i;j++)
            {
                ans[i]=min(ans[i],ans[j]+dp[j+1][i]);
            }
        }

    }
    cout<<ans[len-1]<<endl;
}


int main()
{
    while(cin>>s1>>s2)solve();
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/et3-tsy/p/13199741.html