P1006  传纸条

题目大意:(简化) 一个n*m的矩阵,每一个点对应一个权值。要求你找出两条互不相交的路径从(1,1)出发到右下角(n,m),问两条路径经过点的最大权值之和。

题目思路:

这好像是紫书上原题...,不过紫书上是个图

看一下这个题,如果把两条路径去掉,就是一个非常简单简单的dp。

那么从这个点出发,题目要求两条路径互不相交,那么可以维护两条路径,即一个四维dp。

设dp[i][k][j][l]代表  第一张纸条传到 (i,k),第二张纸条传到 (j,l) 时可以获得最大权值之和。

对于每一个状态(i,k),(j,l)就会有四种情况:

1.第一张纸条向下传递,第二张纸条向下传递

2.第一张纸条向下传递,第二张纸条向右传递

3.第一张纸条向右传递,第二张纸条向下传递

4.第一张纸条向右传递,第二张纸条向右传递

所以每一个状态会由 之前的四个状态得出,但是会有重复情况,比如说 第一张纸条向下传到 (i,k) 第二张纸条向下传到(j,l) ,这个时候我们维护的两个路径就相交了!!所以我们减去重复的部分mp[i][k](该交点的权值),意思就是我们让一条路径经过该点时得到的权值是0,那么该条路径就没经过这个点呗。

所以根据这四个状态再加一个 去重即可得到代码:

#include <bits/stdc++.h>
#include<stdio.h>
#include <string.h>
#include<algorithm>
#pragma GCC optimize(2)
typedef long long ll;
const ll mod=9973;
const int maxn=5e2+5;
const ll INF=100000000000005;
using namespace std;
ll n,m;
ll mp[maxn][maxn];
ll dp[55][55][55][55];
int main()
{
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;i++)
        for(int k=1;k<=m;k++)
            scanf("%lld",&mp[i][k]);
    for(int i=1;i<=n;i++)
        for(int k=1;k<=m;k++)
            for(int j=1;j<=n;j++)
                for(int l=1;l<=m;l++){
                    dp[i][k][j][l]=max(dp[i][k][j][l],dp[i-1][k][j-1][l]+mp[i][k]+mp[j][l]);//down down
                    dp[i][k][j][l]=max(dp[i][k][j][l],dp[i-1][k][j][l-1]+mp[i][k]+mp[j][l]);//down right
                    dp[i][k][j][l]=max(dp[i][k][j][l],dp[i][k-1][j][l-1]+mp[i][k]+mp[j][l]);//right right
                    dp[i][k][j][l]=max(dp[i][k][j][l],dp[i][k-1][j-1][l]+mp[i][k]+mp[j][l]);//right down
                    if(i==j&&k==l) dp[i][k][j][l]-=mp[i][k];
                }
    printf("%lld\n",dp[n][m][n][m]);
}
/***
4 2 1
1 5 6 4
***/

P1140

题目大意:有两个字符串S与T,在每个字符串中可以加入‘-’(即题目中的空碱基)之后可以得到新的序列,问如何加入'-'构造出新序列之后,权值匹配之和最大。权值匹配满足下表:

题目思路:

首先第一波操作没有疑问 应该是 直接建表 找出对应关系

然后分析,应该是个dp题。

类比最长公共子序列的dp。

我们设dp[i][k]代表 我们用S的i个字符与T的j个字符匹配可以产生的最大权值

有以下三种状态:

1.第一个字符串的第i个碱基匹配空碱基

2.第二个字符串的第j个碱基匹配空碱基

3.第一个字符串的第i个碱基匹配第二个字符串的第j个碱基匹配

所以每个点都可以由三个状态转移而来,然后注意一下边界问题:即  第一个字符串第0个碱基与第二个字符串所有碱基匹配,很容易看出,这个时候只能让第二个字符串匹配空碱基,所以 他的来源只有一个状态。同样的第二个字符串的0个碱基匹配也是如此。

得出三个状态+一个边界状态 得到本题AC代码:

#include <bits/stdc++.h>
#include<stdio.h>
#include <string.h>
#include<algorithm>
#pragma GCC optimize(2)
typedef long long ll;
const ll mod=9973;
const int maxn=5e2+5;
const ll INF=100000000000005;
using namespace std;
ll n,m;
ll dp[maxn][maxn],cop[maxn];
char s1[maxn],s2[maxn];
int a[maxn],b[maxn];
ll ta[5][5]={
    {5ll,-1ll,-2ll,-1ll,-3ll},
    {-1ll,5ll,-3ll,-2ll,-4ll},
    {-2ll,-3ll,5ll,-2ll,-2ll},
    {-1ll,-2ll,-2ll,5ll,-1ll},
    {-3ll,-4ll,-2ll,-1ll,0ll}
};
void restart()
{
    for(int i=1;i<=n;i++) {
        if(s1[i]=='A') a[i]=0;
        if(s1[i]=='C') a[i]=1;
        if(s1[i]=='G') a[i]=2;
        if(s1[i]=='T') a[i]=3;
        if(s1[i]=='-') a[i]=4;
    }
    for(int i=1;i<=m;i++) {
        if(s2[i]=='A') b[i]=0;
        if(s2[i]=='C') b[i]=1;
        if(s2[i]=='G') b[i]=2;
        if(s2[i]=='T') b[i]=3;
        if(s2[i]=='-') b[i]=4;
    }
}
int main()
{
    scanf("%lld%s%lld%s",&n,s1+1,&m,s2+1);
    restart();
    for(int i=1;i<=n;i++)
        for(int k=1;k<=m;k++) 
            dp[i][k]=-INF;
    for(int i=1;i<=n;i++) dp[i][0]=dp[i-1][0]+ta[a[i]][4];
    for(int i=1;i<=m;i++) dp[0][i]=dp[0][i-1]+ta[4][b[i]];
    for(int i=1;i<=n;i++){
        for(int k=1;k<=m;k++){
            dp[i][k]=max(dp[i][k],dp[i-1][k-1]+ta[a[i]][b[k]]);
            dp[i][k]=max(dp[i][k],dp[i-1][k]+ta[a[i]][4]);
            dp[i][k]=max(dp[i][k],dp[i][k-1]+ta[4][b[k]]);
        }
    }
    printf("%lld\n",dp[n][m]);
}
/***
4 2 1
1 5 6 4
***/