IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 【训练题52:动态规划 | 优化】Double Strings | 2021牛客暑期多校训练营5 -> 正文阅读

[数据结构与算法]【训练题52:动态规划 | 优化】Double Strings | 2021牛客暑期多校训练营5

题意

  • Double Strings | 2021牛客暑期多校训练营5
    有两个字符串 A , B A,B A,B
    拿出 A A A 的一个子序列 a a a B B B 的一个子序列 b b b
    满足 ∣ a ∣ = ∣ b ∣ |a|=|b| a=b 且字典序 a < b a<b a<b
    问方案数
  • 1 ≤ ∣ A ∣ , ∣ B ∣ ≤ 5000 1\le |A|,|B|\le 5000 1A,B5000

记号

  • 为了后文方便描述,我们做一些记号
    ∣ A ∣ |A| A 表示字符串 A A A 的长度
    ∣ a ∣ |a| a 表示字符串 A A A 的一个子序列 a a a 的长度
    A [ 1 , i ] A[1,i] A[1,i] 表示字符串 A A A 下标从 1 1 1 取到 i i i 的一个子串 ( b a s e 1 base 1 base1
    a < b a<b a<b 表示子序列 a a a 的字典序小于子序列 b b b
    时间复杂度中的 N N N 表示两个字符串的长度的较大值
    S i S_i Si? 表示字符串 S S S 的第 i i i

思路

  • 首先注意到范围,基本只能 O ( N 2 ) O(N^2) O(N2) 去做
    因为两个子序列字典序满足 a < b a<b a<b,所以一定存在 i , j i,j i,j ,满足 a i < b j a_i<b_j ai?<bj?
    那么这个子序列的前部分和后部分怎么样呢?
    请添加图片描述
  • 容易想到, a 前 = b 前 a前=b前 a=b ∣ a 后 ∣ = ∣ b 后 ∣ |a后|=|b后| a=b
    于是我们就把原问题分成了两个子问题
    (1) A [ 1 , i ] A[1,i] A[1,i] B [ 1 , j ] B[1,j] B[1,j] 之间,有多少个相同的子序列
    (2) a 后 a后 a b 后 b后 b 有多少种取法,满足它们的长度相同

第二个子问题

  • 容易想到是和 ∣ A ∣ ? i |A|-i A?i ∣ B ∣ ? j |B|-j B?j 的大小有关系,与后面的字符是无关的
    我们化简一下问题,令第一个串 S S S 第二个串 T T T ,我们从第一个串取出子序列 s s s ,第二个串取出子序列 t t t ,满足 ∣ s ∣ = ∣ t ∣ |s|=|t| s=t方案数,后面简称为方案数
    对于任意的 ∣ S ∣ |S| S ∣ T ∣ |T| T 我们都需要得到,状态数是 O ( N 2 ) O(N^2) O(N2)
    从数学的角度讲,答案为:
    ∑ i = 1 min ? ( ∣ S ∣ , ∣ T ∣ ) C ∣ S ∣ i C ∣ T ∣ i \sum_{i=1}^{\min(|S|,|T|)} C_{|S|}^i C_{|T|}^i i=1min(S,T)?CSi?CTi?
    但是貌似直接算一次时间复杂度为 O ( N ) O(N) O(N),我们总复杂度为 O ( N 3 ) O(N^3) O(N3) ,明显承受不起
    【但是赛后看别人,这个式子可以直接化简的,不过假设我们不知道结论,怎么去做】
    所以我们从 d p dp dp 的角度出发
  • d p [ i ] [ j ] dp[i][j] dp[i][j] 表示第一个串考虑了前 i i i 个位置,第二个串考虑了前 j j j 个位置,方案数是多少
    想到,我们是怎么枚举状态的?
    先是固定 i i i 的位置,然后枚举 j = 1 j=1 j=1 j = ∣ T ∣ j=|T| j=T
    那么,我们现在要去算 d p [ i ] [ j ] dp[i][j] dp[i][j] ,就假设我们已经得到了之前的 d p dp dp 数组
    也就是,第二维从 j ? 1 j-1 j?1 扩到 j j j ,我们新增加的贡献是多少?
    请添加图片描述
  • 第一种情况, T j T_j Tj? 我们不选,那么容易想打方案数就是 d p [ i ] [ j ? 1 ] dp[i][j-1] dp[i][j?1]
    第二种情况, T j T_j Tj? 我们选,由于两个子序列的长度需要相同,那么我们假设 s s s 的最后一位多选一个下标 k k k 的位置
    此时方案数为多少呢?容易想到为 d p [ k ? 1 ] [ j ? 1 ] dp[k-1][j-1] dp[k?1][j?1]
    由于 k k k 的位置有很多种,所以我们需要一个求和。
    综上,方案数为:
    d p [ i ] [ j ] = d p [ i ] [ j ? 1 ] + ∑ k = 1 i d p [ k ? 1 ] [ j ? 1 ] dp[i][j]=dp[i][j-1]+\sum_{k=1}^{i} dp[k-1][j-1] dp[i][j]=dp[i][j?1]+k=1i?dp[k?1][j?1]
    转换一下下标方便计算,变成
    d p [ i ] [ j ] = d p [ i ] [ j ? 1 ] + ∑ k = 0 i ? 1 d p [ k ] [ j ? 1 ] dp[i][j]=dp[i][j-1]+\sum_{k=0}^{i-1} dp[k][j-1] dp[i][j]=dp[i][j?1]+k=0i?1?dp[k][j?1]
  • 由于我们的 i i i 也是递增枚举过来的,自然这个时候 ∑ k = 0 i ? 1 d p [ k ] [ j ? 1 ] \sum_{k=0}^{i-1} dp[k][j-1] k=0i?1?dp[k][j?1] 之前是算出来过的
    我们使用前缀和便可以 O ( 1 ) O(1) O(1) 得到
    然后由于枚举变量的关系,我们可以使用滚动数组 来让前缀和数组变成空间 O ( N ) O(N) O(N)
    在代码中命名为 d p 2 [ i ] [ j ] dp2[i][j] dp2[i][j]
    然后再考虑一下边界,就是 d p 2 [ i ] [ 0 ] = 1 dp2[i][0]=1 dp2[i][0]=1 因为空的子序列也算一种方案
	int L1 = strlen(s1+1);
    int L2 = strlen(s2+1);
    int st = 0;
    for(int i = 0;i <= L1;++i){
        dp2[i][0] = 1;
        pre[0][st^1]++;
        for(int j = 1;j <= L2;++j){
            dp2[i][j] = (dp2[i][j-1] + pre[j-1][st]) % MOD;
            pre[j][st^1] = (pre[j][st^1] + dp2[i][j]) % MOD;
         }
         for(int j = 0;j <= L2;++j){
            pre[j][st] = (pre[j][st] + pre[j][st^1]) % MOD;
            pre[j][st^1] = 0;
         }
    }

第二个子问题的数学做法

  • 我们有:
    ∑ i = 0 m C n i C m i = C n + m m \sum_{i=0}^{m}C_n^iC_m^i=C_{n+m}^m i=0m?Cni?Cmi?=Cn+mm?
    证明方法:右边等价于:从 n + m n+m n+m 个不同的球中摸出 m m m 个球的方案数
    等式左边等价于把 n + m n+m n+m 个球分成 n 、 m n、m nm 两块
    n n n 个球中摸出 i i i 块,从 m m m 个球摸出 m ? i m-i m?i 个球
    得证

第一个子问题

  • 我们之间做过的 L C S LCS LCS (最长公共子序列) 的问题,是求两个串的 L C S LCS LCS
    这里我们的问题变成了求两个串有多少个非空 C S CS CS
    如果 s i ≠ s j s_i\ne s_j si??=sj? ,根据容斥我们可以得到: d p [ i ] [ j ] = d p [ i ] [ j ? 1 ] + d p [ i ? 1 ] [ j ] ? d p [ i ? 1 ] [ j ? 1 ] dp[i][j]=dp[i][j-1]+dp[i-1][j]-dp[i-1][j-1] dp[i][j]=dp[i][j?1]+dp[i?1][j]?dp[i?1][j?1]
    如果 s i = s j s_i=s_j si?=sj?,也根据容斥,我们有 d p [ i ] [ j ] = d p [ i ] [ j ? 1 ] + d p [ i ? 1 ] [ j ] ? d p [ i ? 1 ] [ j ? 1 ] + d p [ i ? 1 ] [ j ? 1 ] + 1 dp[i][j]=dp[i][j-1]+dp[i-1][j]-dp[i-1][j-1] +\color{red}{dp[i-1][j-1]+1} dp[i][j]=dp[i][j?1]+dp[i?1][j]?dp[i?1][j?1]+dp[i?1][j?1]+1
    + 1 +1 +1 是因为我们让 s i 、 s j s_i、s_j si?sj? 单独成为一个子序列
    + d p [ i ? 1 ] [ j ? 1 ] +dp[i-1][j-1] +dp[i?1][j?1] 就是我们让 s i 、 s j s_i、s_j si?sj? 拼到之前的所有满足的子序列之中
    for(int i = 1;i <= L1;++i){
        for(int j = 1;j <= L2;++j){
            if( s1[i] == s2[j] )
                dp[i][j] = (dp[i-1][j] + dp[i][j-1] + 1) % MOD;
            else
                dp[i][j] = (dp[i-1][j] + dp[i][j-1] - dp[i-1][j-1]) % MOD;
        }
    }
  • 那么最后的答案怎么去算呢?
    只要 A i < B j A_i<B_j Ai?<Bj? ,那么答案就是 ( d p [ i ? 1 ] [ j ? 1 ] + 1 ) × d p 2 [ ∣ A ∣ ? i ] [ ∣ B ∣ ? j ] (dp[i-1][j-1] + 1) \times dp2[|A|-i][|B|-j] (dp[i?1][j?1]+1)×dp2[A?i][B?j]
    加一是因为还有一个空的子序列
    然后这里也可以滚动数组滚一下

代码

  • 时间复杂度: O ( N 2 ) O(N^2) O(N2)
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;
typedef long long ll;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x <<  " ] , ";show(args...);}

const int MAX = 5e3+5;
const int MOD = 1e9+7;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const double EPS = 1e-5;

char s1[MAX],s2[MAX];

ll dp[2][MAX];

int dp2[MAX][MAX];
ll pre[MAX][2];
int main()
{


    scanf("%s%s",s1+1,s2+1);
    int L1 = strlen(s1+1);
    int L2 = strlen(s2+1);
    int st = 0;
    for(int i = 0;i <= L1;++i){
        dp2[i][0] = 1;
        pre[0][st^1]++;
        for(int j = 1;j <= L2;++j){
            dp2[i][j] = (dp2[i][j-1] + pre[j-1][st]) % MOD;
            pre[j][st^1] = (pre[j][st^1] + dp2[i][j]) % MOD;
         }
         for(int j = 0;j <= L2;++j){
            pre[j][st] = (pre[j][st] + pre[j][st^1]) % MOD;
            pre[j][st^1] = 0;
         }
    }
    ll ans = 0;

    for(int i = 1;i <= L1;++i){
        for(int j = 1;j <= L2;++j){
            if( s1[i] == s2[j] )
                dp[st^1][j] = (dp[st][j] + dp[st^1][j-1] + 1) % MOD;
            else
                dp[st^1][j] = (dp[st][j] + dp[st^1][j-1] - dp[st][j-1]) % MOD;

            if(s1[i] < s2[j]){
                ans = (ans + (dp[st][j-1] + 1) * 1LL * dp2[L1 - i][L2 - j] % MOD) % MOD;
            }
        }
        for(int j = 1;j <= L2;++j){
            dp[st][j] = dp[st^1][j];
            dp[st^1][j] = 0;
        }
    }
    ans = (ans + MOD) % MOD;
    printf("%lld",ans);
    return 0;
}
  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-08-01 14:44:55  更:2021-08-01 14:45:13 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/15 2:18:05-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码