[线性dp] Color Length UVa1625

题意

输入两个长度分别为n和m的颜色序列,要求按顺序合并成同一个序列,即每次把一个序列开头颜色放到新队列的尾部。对于每种颜色,函数L(c)表示新队列中该种颜色最后一次出现的位置坐标减第一次出现的位置坐标。现求所有L(c)之和的最小值。

题解

在解决本题的过程中,该题中的一些想法得到了实践,并且取得较好的效果。但在写状态转移方程的过程中遇到困难——没有找到指标函数。

自己理解的指标函数是:在状态转移过程中,具体选择哪个决策需要根据每种决策的具体优劣值,或是说每种决策的指标函数值。有时,指标函数就是前一状态的dp值;有时则还需要加上一个转移代价。转移代价就是从上一状态转移到当前转态的代价,它是和定义的dp统一的。

同时,此题还有一个陷阱。如果将dp(i,j)定义为两个序列分别处理到i和j位置处的答案数,而答案数是L(c)之和,L(c)的计算需要具体的位置坐标吗,这就导致我们很难求出该状态下的指标函数。因为我们只知道上一状态的dp值,而不知道具体的排列状况。解决这个问题有两种思路,一是记录下每一状态的排列状况(通过增加转态或另外保存),但这种方法(针对本题)会使转态十分复杂并且消耗大量空间。而另一种方法是改变dp的定义。

在此之前,先提出自己的猜想。dp问题中所求答案有两种形式:一是转移代价与位置无关,二是转移代价与位置有关。这里的位置是指过程中的某阶段。对于与位置无关的dp,其指标函数往往简单,转移代价只需根据当前状态便可算出,例如+1或是+w[i]等。而与位置有关的dp又有两种情况:一种是位置信息可以保存,例如 Partitioning by Palindromes。可以在可接受的时间复杂度内计算出转移代价,或是通过预处理出所有可能的转移代价。另一种则如本题,转移代价与位置有关且位置信息无法保存。对于这样的题,我们不直接将dp定义为答案数,而是定义为对最终答案的贡献。这样,转移代价也就不着眼于子问题,而是某一步骤。

回到本题,当我们从某一状态转移到另一转态时,我们仅能知道的是已经有哪些颜色合并进了新序列。转移时我们是将一个颜色加入序列,在这个过程中,如果新序列中的某种颜色已经全部用掉,那么新加的颜色对其L(c)值无影响。而如果有已经加入新序列而未结束的颜色来说,会使其L(c)值加一,而这个L(c)值是全局意义上的。

综上,代码如下:

AC代码

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>
using namespace std;

const int maxn = 5000+5;
const int INF = 0x3f3f3f3f;

int dp[maxn][maxn];
char a[maxn],b[maxn];
int color[2][27][2];
bool vc[27];

int w(int i, int j){
    i--;j--;
    int cnt = 0;
    for(int k = 0; k<26; k++)if(vc[k]){
        if(color[0][k][0] != -1 && color[1][k][0]!=-1){
            if(!((i<color[0][k][0]&&j<color[1][k][0])||
                (i>=color[0][k][1]&&j>=color[1][k][1])))cnt++;
        }
        else if(color[0][k][0] != -1){
            if(color[0][k][0]<=i&&i<color[0][k][1]) cnt++;
        }
        else{
            if(color[1][k][0]<=j&&j<color[1][k][1]) cnt++;
        }
    }
    return cnt;
}

int main(){
    int T;
    scanf("%d", &T);
    while(T--){
        scanf("%s", a);
        scanf("%s", b);
        int n = strlen(a), m = strlen(b);
        memset(vc, false, sizeof(vc));
        for(int i = 0; i<2; i++)
        for(int j = 0; j<27;j++)
        for(int k = 0; k<2; k++){
            color[i][j][k] = -1;
        }
        bool cb[26];
        memset(cb, false, sizeof(cb));
        for(int i = 0; i<n; i++)if(!cb[a[i]-'A']){
            vc[a[i]-'A'] = cb[a[i]-'A'] = true;
            int j = n-1;
            while(a[j] != a[i]) j--;
            color[0][a[i]-'A'][0] = i;
            color[0][a[i]-'A'][1] = j;
        }
        memset(cb, false, sizeof(cb));
        for(int i = 0; i<m; i++)if(!cb[b[i]-'A']){
            vc[b[i]-'A'] = cb[b[i]-'A'] = true;
            int j = m-1;
            while(b[j] != b[i]) j--;
            color[1][b[i]-'A'][0] = i;
            color[1][b[i]-'A'][1] = j;
        }
        for(int i = 1; i<=n; i++)dp[i][0] = dp[i-1][0] + w(i-1,0);
        for(int i = 1; i<=m; i++)dp[0][i] = dp[0][i-1] + w(0, i-1);
        for(int i = 1; i<=n; i++){
            for(int j = 1; j<=m; j++){
                dp[i][j] = min(dp[i-1][j]+w(i-1, j), dp[i][j-1]+w(i, j-1));
            }
        }
        printf("%d\n",dp[n][m]);
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
疫情居家办公系统管理系统按照操作主体分为管理员和用户。管理员的功能包括办公设备管理、部门信息管理、字典管理、公告信息管理、请假信息管理、签到信息管理、留言管理、外出报备管理、薪资管理、用户管理、公司资料管理、管理员管理。用户的功能等。该系统采用了MySQL数据库,Java语言,Spring Boot框架等技术进行编程实现。 疫情居家办公系统管理系统可以提高疫情居家办公系统信息管理问题的解决效率,优化疫情居家办公系统信息处理流程,保证疫情居家办公系统信息数据的安全,它是一个非常可靠,非常安全的应用程序。 管理员权限操作的功能包括管理公告,管理疫情居家办公系统信息,包括外出报备管理,培训管理,签到管理,薪资管理等,可以管理公告。 外出报备管理界面,管理员在外出报备管理界面中可以对界面中显示,可以对外出报备信息的外出报备状态进行查看,可以添加新的外出报备信息等。签到管理界面,管理员在签到管理界面中查看签到种类信息,签到描述信息,新增签到信息等。公告管理界面,管理员在公告管理界面中新增公告,可以删除公告。公告类型管理界面,管理员在公告类型管理界面查看公告的工作状态,可以对公告的数据进行导出,可以添加新公告的信息,可以编辑公告信息,删除公告信息
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值