题解/算法 {5728. 截取子串}

题解/算法 {5728. 截取子串}

@LINK: https://www.acwing.com/problem/content/description/5731/;

首先 你要理解字符串切割的本质, 比如"abcdef", 那么{"a", "cde"} 是一个方案, 换句话说 他是若干个{[Li, Ri]}区间的集合 且这些区间没有交集; (他的本质 就是若干个区间的集合 而不是若干个下标 即他和隔板法无关);

因此 任意一个方案 都可以写成是一个区间的序列 比如[ [0-1], [3-4], [7-9] ]; 其实 这就很像DP了, 我们根据其最后的区间[7-9]进行枚举, 换句话说 我们区间[l,r] 然后求出来有多少个方案 他们的最后区间是当前[l,r]区间;

看一个错误做法: 我们枚举S中所有T的子串 即枚举l,r满足S[l-r] = T, 然后我们求 所有形如[..., [L,R]]的方案 其中L<=l, R>=r;
这是错误的, 因为[L,R]他里面可以有多个T子串 就导致 你的方案重复计算了, 比如S = "abacababa", T="aba", 那么对于["abac", "ababa"]这个方案, 当l=4,r=6时 会枚举到L=4,R=8 即这个方案, 而当l=6,r=8时 也会枚举到L=4,R=8 这个方案 重复了;

正确的做法是: 对于[..., [L,R]] 我们把R端点给固定住, 换句话说 我们枚举所有的R 然后再求形如[..., [?,R]]的方案个数;
我们令&Lef为 满足S[Lef, R]区间里包含T的最大的Lef, 令&Cont[r]: S[0...r]里 形如[...,[?,<=r]]的方案个数, 于是此时对于形如[..., [?,R]]的方案 其中? 一定是<= Lef 更准确的说 ?的范围是 [0, Lef], 也就是 对于任意的? = [0, Lef] 我们执行cur += Cont[?-1] + 1 (加一的意思是 [?,R]是单独的一个) 此时cur就是所有形如[..., [?,R]]的方案个数, 接下来更新ContCont[Rig, ..., N-1] += cur;

vector<Mod_> Cont( S.size(), 0);
Mod_ ANS = 0;
int Lef = -1;
FOR_( rig, T.size()-1, S.size()-1){
    if( S.substr( rig-T.size()+1, T.size()) == T){
        Lef = rig - T.size() + 1;
    }
    if( Lef == -1){ continue;}
    Mod_ cur = 0;
    FOR_( l, 0, Lef){
        cur += 1;
        if( l > 0){ cur += Cont[ l-1];}
    }
    ANS += cur;
    FOR_( r, rig, S.size()-1){ Cont[r] += cur;}
}
cout<< ANS.Value;

于是 区间修改 + 区间查询, 用懒标记线段树即可;
然后那个获取S中 所有等于T的子串下标, 用KMP或字符串哈希 都可以;

FOR_( rig, T.size()-1, S.size()-1){
	if( HS.GetHash_SubString( rig-T.size()+1, rig) == HT){ // 字符串哈希
		Lef = rig - T.size() + 1;
    }
    if( Lef == -1){ continue;}

    Mod_ cur = 0;
    cur += 1; // [0...rig]
    if( Lef-1 >= 0){
        auto ret = Seg.Query_Interval( 0, Lef-1);
        ret.Sum += Lef;
        cur += ret.Sum;
    }

    ANS += cur;
    if( rig <= (int)S.size()-1){
        Seg.Modify_Interval( rig, S.size()-1, {cur});
    }
}
cout<< ANS.Value;
  • 10
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值