Color Length-颜色的长度(UVA1625,ACM/ICPC Daejeon 2011)(DP找最优值,较难,转化DP思路,指标函数优化计算方式)

6 篇文章 0 订阅

前言

这道题过得很悬啊,5000*5000*25的数据3s竟然过了……本来加的优化全部都可以删了,在我看来这是一个伪 O(mn) O ( m n ) ~

题目

这里好不容易找到了PDF版的
附两个传送门(UVA有点慢):
vjudge
UVA
题目描述

题目大意:

输入两个长度为n,m(n,m<=5000)的颜色序列a串和b串,要求按顺序合并成同一个序列,即每次可以吧一个序列开头的颜色放到新序列的尾部.现在对于每个颜色来说,其跨度L(c)等于最大位置和最小位置之差,现在请你找出一种合并方式使得所有L(c)总和最小。
贴心的我把输入输出打一遍好让你们测试~:
in i n

2
AAABBCY
ABBBCDEEY
GBYY
YRRGB

out o u t

10
12

思路

在这里我们很容易就写出了DP状态的定义:
f[i][j]:ai,bj f [ i ] [ j ] : a 串 移 走 前 i 个 , b 串 移 走 前 j 个 的 费 用
注意,这里的费用说的极为模糊不易于我们写代码,因为在这道题中状态转移较为复杂,我们不易于记录状态当一颜色第一次出现在合并序列时,我们不知道它什么时候结束,同样对于一颜色最后一次出现在序列里时,我们并不知道它什么时候开始的。
所以我们并不能一个一个颜色的转移.
那怎么做呢?我们就直接对于一一开始但未结束的颜色一次一次地累加,那这里的费用算到最后就是答案.就比较清晰了
状态转移方程的一部分是:
f[i][j]=min(f[i1][j],f[i][j1]) f [ i ] [ j ] = m i n ( f [ i − 1 ] [ j ] , f [ i ] [ j − 1 ] )
注意只要有越界就不考虑这种情况
意思是我们现在合并的序列长度为 i+j i + j ,我们要从状态 i+j1 i + j − 1 后面填一个a串的颜色或b串的颜色转移到 i+j i + j 同时必须满足两状态中必须选a串的长度相等或b串的长度相等,因为这是DP
我们一开始就要预处理出对于一个颜色在a串和b串分别第一次出现,最后一次出现的位置(这里记为fa,fb,la,lb)
我们在枚举i,j中在增加一个循环,枚举颜色k,只要对于当前位置已开始未结束,当前状态就加1
我们把加1单独用cnt来存,那么状态转移方程式就是:
f[i][j]=min(f[i1][j],f[i][j1])+cnt f [ i ] [ j ] = m i n ( f [ i − 1 ] [ j ] , f [ i ] [ j − 1 ] ) + c n t
注意枚举k判断时间开始和结束这样判断比较好一些:

(fa[k]<=i||fb[k]<=j)&&(i<la[k]||j<lb[k])

对于fa,fb,la,lb来说初值很重要,fa,fb初值全为INF,la,lb初值全为0,因为这样说明所有颜色都不能选.而预处理完后没有改变的就在DP中不合法了(也就是不会选到)

代码

#include<set>
#include<map>
#include<ctime>
#include<queue>
#include<cmath>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
#define INF 0x3f3f3f3f
#define MAXN 5000
int f[MAXN+5][MAXN+5];
char a[MAXN+5],b[MAXN+5];
int fa[30],fb[30],la[30],lb[30];
int main(){//f[i][j]:a串移除i个,b串移除j个中
    int T;scanf("%d",&T);//有多少颜色出现的计算最小值
    while(T--){
        scanf("%s %s",a+1,b+1);
        memset(la,0,sizeof(la));//初始化
        memset(lb,0,sizeof(lb));
        memset(fa,0x3f,sizeof(fa));
        memset(fb,0x3f,sizeof(fb));
        int n=strlen(a+1),m=strlen(b+1);
        for(int i=1;i<=n;i++){//预处理
            if(fa[a[i]-'A']==INF) fa[a[i]-'A']=i;
            la[a[i]-'A']=i;
        }
        for(int i=1;i<=m;i++){
            if(fb[b[i]-'A']==INF) fb[b[i]-'A']=i;
            lb[b[i]-'A']=i;
        }
        for(int i=0;i<=n;i++){//DP
            for(int j=0;j<=m;j++){
                int cnt=0;//统计颜色对于当前合并位置
                for(int k=0;k<=25;k++)//已开始但未结束的种数
                    if((fa[k]<=i||fb[k]<=j)&&(i<la[k]||j<lb[k]))
                        cnt++;
                if(i&&j) f[i][j]=min(f[i-1][j],f[i][j-1])+cnt;
                else if(i) f[i][j]=f[i-1][j]+cnt;//越界处理
                else if(j) f[i][j]=f[i][j-1]+cnt;
            }
        }
        printf("%d\n",f[n][m]);
    }
    return 0;
}

题外话

这个数据规模我也不知道我是怎么过的..
.但是我们可以优化,明显可以发现,cnt可以维护而不是每次重复计算,我们对于每个颜色可以建立一个整型vis来表示二进制中上面判断颜色已开始但未结束的四个信息是否从成立
cnt直接定义在Dp外,只要每次在外层循环更新vis时发现vis%4和vis/4均有值cnt就+1,否则就-1
而进入内层循环时要用一个tmp把当前vis复制下来用tmp更新cnt,否则vis在下一次就要耗时恢复了.
这样就不用枚举内层的k了时间复杂度就真的变为 O(nm) O ( n m )

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值