[线性DP] UVa1625 颜色长度 (LRJ代码)(多状态DP)

题目

这里写图片描述

思路

1.本题可以说是有难度了,首先思路就不好想到,DP有两个状态,两个状态分别有自己的状态转移方程。其次代码实现起来,细节很多,自己尝试写了一下,debug了一下午也没搞出来,只能搬来LRJ的代码了。


2.回顾一下本题的思考过程:
首先,可以想到LCS,因为每次都是从两个序列种的一个拿元素。最基本的状态就想到了,d(i,j),表示从两个序列分别拿走i,j个元素后最小的指标函数值。问题就出在指标函数的上,本题的指标函数应当是L(c)之和,比较复杂。
容易想到的方式是,让状态变成多维,记录每个元素开始的地方,从而拿进元素时算出L(c)之和。但这样的话时间和空间都无法承受。
正确的方法是,再来一个状态c(i,j),表示已经开始但没有结束的颜色数量,从而可以根据上一个d(i2,j2)来一步算出这次的d(i1,j1),这样的话就引入了状态c(i,j)作为辅助计算的状态,同时这个状态c(i,j)也需要转移计算。


3.状态定义:

  • d(i,j):从两个序列拿走i,j组成的子序列,最小的L(c)之和。
  • c(i,j):从两个序列拿走i,j组成的子序列,有多少颜色已经开始但没有结束。

4.初状态:都是0
5.答案:d(n,m)
6.状态转移方程:

d ( i , j ) = m i n { d ( i 1 , j ) + c ( i 1 , j ) , d ( i , j 1 ) + c ( i , j 1 ) }

c ( i , j ) = { c ( i 1 , j ) + 1 | e l e m e n t ( i , j )   i s   a   s t a r t c o l o r } o r { c ( i 1 , j ) 1 | e l e m e n t ( i , j )   i s   a n   e n d c o l o r } o r   { c ( i , j 1 ) + 1 | e l e m e n t ( i , j )   i s   a   s t a r t c o l o r } o r   { c ( i 1 , j 1 ) 1 | e l e m e n t ( i , j )   i s   a n   e n d c o l o r }



7.滚动数组:
本题的滚动数组设计的时候,还是要机灵一下的。
滚动数组是两维的,为什么不能设置成一维呢?
因为滚动数组的原理是,利用某个旧元素创造一个新元素,再用这个新元素将某个旧元素覆盖。
在本题中,i-1的转移是常见的转移,只有当j逆序枚举时才能正常工作。
而j-1的转移也在同时发生,只有当j正序枚举时才能正常工作。
所以这两个转移,用滚动数组时冲突了,所以我们设置成两维,一维保存i-1的数据,一维保存新数据,并在新数据的一维正序枚举。

根据本人的验证,并不需要将滚动数组二维化就能AC。原因是j-1要求正序枚举,而i-1实际对枚举顺序无要求,所以正序枚举即可。


并且LRJ使用了“^”,简化了很多代码,这里要学习这个“^”的使用:

^,按位异或运算符。
参与运算的两个值,如果两个相应位相同,则结果为0,否则为1。即:0^0=0, 1^0=1, 0^1=1, 1^1=0
x^1的作用:当x为0时返回1,当x为1时返回0。有点类似于!,但只会返回0和1。

代码

LRJ原版

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#define _for(i,a,b) for(int i = (a); i<(b); i++)
#define _rep(i,a,b) for(int i = (a); i<=(b); i++)
using namespace std;

const int INF = 100000000;
const int maxn = 5000 + 5;

char p[maxn], q[maxn];  // 从位置1开始
int sp[26], ep[26], sq[26], eq[26];  // sq:start position of q
int d[2][maxn], c[2][maxn];   // 使用滚动数组,注意这里的滚动数有两层

int main() {
    int T;
    scanf("%d", &T);
    while (T--) {
        scanf("%s%s", p + 1, q + 1);

        int n = strlen(p + 1);
        int m = strlen(q + 1);
        _rep(i, 1, n) p[i] -= 'A';
        _rep(i, 1, m) q[i] -= 'A';

        // 计算s和e
        _for(i, 0, 26) { sp[i] = sq[i] = INF; ep[i] = eq[i] = 0; }  // 此处的初始化很关键
        _rep(i, 1, n) {
            sp[p[i]] = min(sp[p[i]], i);
            ep[p[i]] = i;
        }
        _rep(i, 1, m) {
            sq[q[i]] = min(sq[q[i]], i);
            eq[q[i]] = i;
        }

        // dp
        int t = 0;
        memset(c, 0, sizeof(c));
        memset(d, 0, sizeof(d));
        _rep(i, 0, n) {
            _rep(j, 0, m) {
                if (i == 0 && j == 0) continue;

                // 计算d
                int v1 = INF, v2 = INF;
                if (i) v1 = d[t ^ 1][j] + c[t ^ 1][j];
                if (j) v2 = d[t][j - 1] + c[t][j - 1];
                d[t][j] = min(v1, v2);

                // 计算c
                if (i) {
                    c[t][j] = c[t ^ 1][j];
                    if (sp[p[i]] == i && sq[p[i]] > j) c[t][j]++;
                    if (ep[p[i]] == i && eq[p[i]] <= j) c[t][j]--;
                }
                else if (j) {
                    c[t][j] = c[t][j - 1];
                    if (sq[q[j]] == j && sp[q[j]] > i) c[t][j]++;
                    if (eq[q[j]] == j && ep[q[j]] <= i) c[t][j]--;
                }
            }
            t ^= 1;
        }
        printf("%d\n", d[t ^ 1][m]);
    }

    return 0;
}

滚动数组去二维

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#define _for(i,a,b) for(int i = (a); i<(b); i++)
#define _rep(i,a,b) for(int i = (a); i<=(b); i++)
using namespace std;

const int INF = 100000000;
const int maxn = 5000 + 5;

char p[maxn], q[maxn];  // 从位置1开始
int sp[26], ep[26], sq[26], eq[26];  // sq:start position of q
int d[maxn], c[maxn];   // 使用滚动数组,注意这里的滚动数有两层

int main() {
    int T;
    scanf("%d", &T);
    while (T--) {
        scanf("%s%s", p + 1, q + 1);

        int n = strlen(p + 1);
        int m = strlen(q + 1);
        _rep(i, 1, n) p[i] -= 'A';
        _rep(i, 1, m) q[i] -= 'A';

        // 计算s和e
        _for(i, 0, 26) { sp[i] = sq[i] = INF; ep[i] = eq[i] = 0; }  // 此处的初始化很关键
        _rep(i, 1, n) {
            sp[p[i]] = min(sp[p[i]], i);
            ep[p[i]] = i;
        }
        _rep(i, 1, m) {
            sq[q[i]] = min(sq[q[i]], i);
            eq[q[i]] = i;
        }

        // dp
        memset(c, 0, sizeof(c));
        memset(d, 0, sizeof(d));
        _rep(i, 0, n) {
            _rep(j, 0, m) {
                if (i == 0 && j == 0) continue;

                // 计算d
                int v1 = INF, v2 = INF;
                if (i) v1 = d[j] + c[j];
                if (j) v2 = d[j - 1] + c[j - 1];
                d[j] = min(v1, v2);

                // 计算c
                if (i) {
                    c[j] = c[j];
                    if (sp[p[i]] == i && sq[p[i]] > j) c[j]++;
                    if (ep[p[i]] == i && eq[p[i]] <= j) c[j]--;
                }
                else if (j) {
                    c[j] = c[j - 1];
                    if (sq[q[j]] == j && sp[q[j]] > i) c[j]++;
                    if (eq[q[j]] == j && ep[q[j]] <= i) c[j]--;
                }
            }
        }
        printf("%d\n", d[m]);
    }

    return 0;
}

猜你喜欢

转载自blog.csdn.net/icecab/article/details/80957546