Typesetting math: 100%

[题解]AT_abc272_f [ABC272F] Two Strings

思路

实际上对于一个字符串 S 进行一个 f(S,x) 的操作本质上就是在 S+S 中截取一段长度为 n 的子串。

于是你不难想到把 A,B 拼起来,形成一个字符串 S=A+A+B+B,然后比较后缀。你发现这是对的,因为两个串的字典序大小从前往后比较的,因此你尽管是比较的后缀,本质上还是比较的两个子串。

比较后缀大小,直接 SA 得出 rk 数组。再次回到题目中,要求 f(A,i)f(B,j) 的数量,你发现这个条件等效于比较两个后缀 rk 的大小。

但是直接枚举 i,j 是不现实的,但是你可以先将 f(A,i)rk 值丢到一个桶里面,然后对于所有的 f(B,j) 对答案的贡献就是在桶中 rk 小于等于它本身 rk 的值的数量。因此对桶做一次前缀和即可。

但是本题中有一个坑点在与字符串应当在 A,B 字符串交界处插入一个极小的字符,在最后插入一个极大的字符。

中间插入极小字符是因为当 A=B 时,B1 就会影响两个后缀的大小关系;最后插入极大字符的原因是,本题求的是小于等于的情况,因此当两个子串相同时也应该记录答案,但是在 SA 中,rk 的大小就会乱飘,为了使我们可以通过上述方法求答案我们应该保证当 f(A,i)=f(B,j) 时,后者排在后面。

Code

#include <bits/stdc++.h>  
#define re register  
#define int long long  
  
using namespace std;  
  
const int N = 1e6 + 10;  
int len,n,m = 250,ans;  
int sum[N];  
int sa[N],rk[N],prk[N],tmp[N],cnt[N];  
char a[N],b[N],s[N];  
  
inline int read(){  
    int r = 0,w = 1;  
    char c = getchar();  
    while (c < '0' || c > '9'){  
        if (c == '-') w = -1;  
        c = getchar();  
    }  
    while (c >= '0' && c <= '9'){  
        r = (r << 3) + (r << 1) + (c ^ 48);  
        c = getchar();  
    }  
    return r * w;  
}  
  
signed main(){  
    len = read();  
    scanf("%s%s",a + 1,b + 1);  
    for (re int i = 1;i <= len;i++) s[++n] = a[i];  
    for (re int i = 1;i <= len;i++) s[++n] = a[i];  
    s[++n] = '&';  
    for (re int i = 1;i <= len;i++) s[++n] = b[i];  
    for (re int i = 1;i <= len;i++) s[++n] = b[i];  
    s[++n] = '|';  
    for (re int i = 1;i <= n;i++){  
        rk[i] = s[i];  
        cnt[rk[i]]++;  
    }  
    for (re int i = 1;i <= m;i++) cnt[i] += cnt[i - 1];  
    for (re int i = n;i;i--) sa[cnt[rk[i]]--] = i;  
    for (re int w = 1;w < n;w <<= 1){  
        int num = 0,p = 0;  
        for (re int i = n - w + 1;i <= n;i++) tmp[++num] = i;  
        for (re int i = 1;i <= n;i++){  
            if (sa[i] > w) tmp[++num] = sa[i] - w;  
        }  
        for (re int i = 1;i <= m;i++) cnt[i] = 0;  
        for (re int i = 1;i <= n;i++) cnt[rk[i]]++;  
        for (re int i = 1;i <= m;i++) cnt[i] += cnt[i - 1];  
        for (re int i = n;i;i--) sa[cnt[rk[tmp[i]]]--] = tmp[i];  
        for (re int i = 1;i <= n;i++) prk[i] = rk[i];  
        for (re int i = 1;i <= n;i++){  
            if (prk[sa[i]] == prk[sa[i - 1]] && prk[sa[i] + w] == prk[sa[i - 1] + w]) rk[sa[i]] = p;  
            else rk[sa[i]] = ++p;  
        }  
        if (p == n) break;  
        m = p;  
    }  
    for (re int i = 1;i <= len;i++) sum[rk[i]]++;  
    for (re int i = 1;i <= 1e6;i++) sum[i] += sum[i - 1];  
    for (re int i = 2 * len + 2;i <= 3 * len + 1;i++) ans += sum[rk[i]];  
    printf("%lld",ans);  
    return 0;  
}  

作者:WaterSun

出处:https://www.cnblogs.com/WaterSun/p/18262933

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   WBIKPS  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
点击右上角即可分享
微信分享提示