codeforces 1499E DP

       给定两个串 x x x y y y。定义 z z z 为从 x x x 串和 y y y 串的归并而成的字符串,归并顺序自定义,要求得到的 z z z 串相邻字符不相同。问你 x x x y y y所有子串能构成 z z z 的方案数有多少种。

       定义 d p [ i ] [ j ] [ a ] [ b ] [ c ] dp[i][j][a][b][c] dp[i][j][a][b][c] d p [ i ] [ j ] dp[i][j] dp[i][j] 表示前面的长度为 i i i x x x 串和长度为 j j j y y y 串的所有子串的方案数。

        a a a 代表最后一个字符结尾为 x x x 串字符还是 y y y 串字符( 0 0 0 表示 x x x 1 1 1 表示 y y y)。

        b b b 代表是否存在 x x x 串字符,( 0 0 0 表示不存在, 1 1 1 表示存在)。

        c c c 代表是否存在 y y y 串字符,( 0 0 0 表示不存在, 1 1 1 表示存在)。

       于是我们存在四种转移情况:

       1.上一个字符为 x x x 串字符,当前字符为 y y y 串字符。

       转移后的情况一定同时含有 x x x 串字符和 y y y 串字符,且以 y y y 串字符结尾,所以当前状态为 d p [ i ] [ j ] [ 1 ] [ 1 ] [ 1 ] dp[i][j][1][1][1] dp[i][j][1][1][1],而前一个状态可能同时存在 x x x 串字符和 y y y 串字符,也可能只有 x x x 串字符。所以是 d p [ i ] [ j − 1 ] [ 0 ] [ 1 ] [ 1 ] + d p [ i ] [ j − 1 ] [ 0 ] [ 1 ] [ 0 ] dp[i][j-1][0][1][1]+dp[i][j-1][0][1][0] dp[i][j1][0][1][1]+dp[i][j1][0][1][0]。其他情况类似分析即可,不再赘述。

       2.上一个字符为 y y y 串字符,当前字符为 x x x 串字符。

       3.上一个字符为 x x x 串字符,当前字符为 x x x 串字符。

       4.上一个字符为 y y y 串字符,当前字符为 y y y 串字符。

       几个 t i p s tips tips

       1.每加入一个字符时都要额外加入统计,子串方案数加一,也就是把当前字符作为子串来考虑。(对应就是代码的 d p [ i ] [ j ] [ 0 ] [ 1 ] [ 0 ] + + dp[i][j][0][1][0]++ dp[i][j][0][1][0]++ d p [ i ] [ j ] [ 1 ] [ 0 ] [ 1 ] + + dp[i][j][1][0][1]++ dp[i][j][1][0][1]++

       2.每次统计只统计同时存在 x x x 串和 y y y 串字符的子串的方案数,因为题目要求两个子串长度都要大于 0 0 0

       3.为什么输入的字符串从 1 1 1 开始,统计子串方案时却从 0 0 0 开始,因为需要统计只有一个串的字符的情况用于辅助计算,所以需要从 0 0 0 开始。

#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define endl '\n'
typedef long long LL;
const int maxn = 2e3 + 1;
const LL mod = 998244353;

char x[maxn], y[maxn];
LL dp[maxn][maxn][2][2][2]; // 0 x, 1 y,

int main(){
    IOS;
    LL ans = 0;
    cin >> (x + 1) >> (y + 1);
    x[0] = '?';
    y[0] = '?';
    for(int i = 0; x[i]; i++){
        for(int j = 0; y[j]; j++){
            if(i && x[i] != x[i - 1]){
                for(int k = 0; k < 2; k++){
                    dp[i][j][0][1][k] += dp[i - 1][j][0][1][k];
                }
            }
            if(j && y[j] != y[j - 1]){
                for(int k = 0; k < 2; k++){
                    dp[i][j][1][k][1] += dp[i][j - 1][1][k][1];
                }
            }
            if(i) dp[i][j][0][1][0]++;
            if(j) dp[i][j][1][0][1]++;
            if(i && j && x[i] != y[j]){
                dp[i][j][0][1][1] += dp[i - 1][j][1][1][1] + dp[i - 1][j][1][0][1];
                dp[i][j][1][1][1] += dp[i][j - 1][0][1][1] + dp[i][j - 1][0][1][0];
            }
            for(int a = 0; a < 2; a++)
                for(int b = 0; b < 2; b++)
                    for(int c = 0; c < 2; c++)
                        dp[i][j][a][b][c] %= mod;
            for(int a = 0; a < 2; a++) ans += dp[i][j][a][1][1];
            ans %= mod;
        }
    }
    cout << ans << endl;
}
/*
 2
5 2
1000 2
 */
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
区间DP是一种动态规划的方法,用于解决区间范围内的问题。在Codeforces竞赛中,区间DP经常被用于解决一些复杂的字符串或序列相关的问题。 在区间DP中,dp[i][j]表示第一个序列前i个元素和第二个序列前j个元素的最优解。具体的转移方程会根据具体的问题而变化,但是通常会涉及到比较两个序列的元素是否相等,然后根据不同的情况进行状态转移。 对于区间长度为1的情况,可以先进行初始化,然后再通过枚举区间长度和区间左端点,计算出dp[i][j]的值。 以下是一个示例代码,展示了如何使用区间DP来解决一个字符串匹配的问题: #include <cstdio> #include <cstring> #include <string> #include <iostream> #include <algorithm> using namespace std; const int maxn=510; const int inf=0x3f3f3f3f; int n,dp[maxn][maxn]; char s[maxn]; int main() { scanf("%d", &n); scanf("%s", s + 1); for(int i = 1; i <= n; i++) dp[i][i] = 1; for(int i = 1; i <= n; i++) { if(s[i] == s[i - 1]) dp[i][i - 1] = 1; else dp[i][i - 1] = 2; } for(int len = 3; len <= n; len++) { int r; for(int l = 1; l + len - 1 <= n; l++) { r = l + len - 1; dp[l][r] = inf; if(s[l] == s[r]) dp[l][r] = min(dp[l + 1][r], dp[l][r - 1]); else { for(int k = l; k <= r; k++) { dp[l][r] = min(dp[l][r], dp[l][k] + dp[k + 1][r]); } } } } printf("%d\n", dp[n]); return 0; } 希望这个例子能帮助你理解区间DP的基本思想和应用方法。如果你还有其他问题,请随时提问。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值