• BZOJ4566 [Haoi2016]找相同字符 【后缀数组】


    题目

    给定两个字符串,求出在两个字符串中各取出一个子串使得这两个子串相同的方案数。两个方案不同当且仅当这两
    个子串中有一个位置不同。

    输入格式

    两行,两个字符串s1,s2,长度分别为n1,n2。1 <=n1, n2<= 200000,字符串中只有小写字母

    输出格式

    输出一个整数表示答案

    输入样例

    aabb

    bbaa

    输出样例

    10

    题解

    先考虑暴力怎么做
    我们枚举两个串的各自一个后缀suffix(i)和suffix(j)
    则他们对答案的贡献是LCP(suffix(i),suffix(j))
    如此得到一个(O(n^3))的算法

    当然如果你知道后缀数组,可以(O(1))求LCP,可以优化到(O(n^2))

    当然如果你知道后缀数组的套路,用一个单调栈扫一遍height[]可以做到(O(nlogn))【主要复杂度在求后缀数组】
    具体这样:
    我们知道,两个后缀之间的LCP是他们之间height的最小值
    如果给出一个位置的后缀,想求这个位置之前所有的后缀与这个位置的LCP之和之类的东西,由于求最小值是一路过去的,所以前面的后缀的LCP不会比后面的大,所以整体是单调不下降的,可以用单调栈处理

    最后我们只需要分A、B串各用单调栈扫两次统计出答案就可以了

    #include<iostream>
    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define LL long long int
    #define REP(i,n) for (int i = 1; i <= (n); i++)
    #define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt)
    #define BUG(s,n) for (int i = 1; i <= (n); i++) cout<<s[i]<<' '; puts("");
    using namespace std;
    const int maxn = 400005,maxm = 100005,INF = 1000000000;
    inline int read(){
    	int out = 0,flag = 1; char c = getchar();
    	while (c < 48 || c > 57) {if (c == '-') flag = -1; c = getchar();}
    	while (c >= 48 && c <= 57) {out = (out << 3) + (out << 1) + c - '0'; c = getchar();}
    	return out * flag;
    }
    int sa[maxn],rank[maxn],height[maxn],t1[maxn],t2[maxn],bac[maxn],n,m;
    char s[maxn];
    int len;
    LL ans;
    void getSA(){
    	int *x = t1,*y = t2; m = 256;
    	for (int i = 0; i <= m; i++) bac[i] = 0;
    	for (int i = 1; i <= n; i++) bac[x[i] = s[i]]++;
    	for (int i = 1; i <= m; i++) bac[i] += bac[i - 1];
    	for (int i = n; i; i--) sa[bac[x[i]]--] = i;
    	for (int k = 1; k <= n; k <<= 1){
    		int p = 0;
    		for (int i = n - k + 1; i <= n; i++) y[++p] = i;
    		for (int i = 1; i <= n; i++) if (sa[i] - k > 0) y[++p] = sa[i] - k;
    		for (int i = 0; i <= m; i++) bac[i] = 0;
    		for (int i = 1; i <= n; i++) bac[x[y[i]]]++;
    		for (int i = 1; i <= m; i++) bac[i] += bac[i - 1];
    		for (int i = n; i; i--) sa[bac[x[y[i]]]--] = y[i];
    		swap(x,y);
    		p = x[sa[1]] = 1;
    		for (int i = 2; i <= n; i++)
    			x[sa[i]] = (y[sa[i]] == y[sa[i - 1]] && y[sa[i] + k] == y[sa[i - 1] + k] ? p : ++p);
    		if (p >= n) break;
    		m = p;
    	}
    	for (int i = 1; i <= n; i++) rank[sa[i]] = i;
    	for (int i = 1,k = 0; i <= n; i++){
    		if (k) k--;
    		int j = sa[rank[i] - 1];
    		while (s[i + k] == s[j + k]) k++;
    		height[rank[i]] = k;
    	}
    }
    int st[maxn],cnt[maxn],top;
    LL cal[maxn];
    void solve(){
    	for (int i = 1; i <= n; i++){
    		if (!height[i]) {top = 0; continue;}
    		int tot = sa[i - 1] <= len ? 1 : 0;
    		while (top && st[top] > height[i]) tot += cnt[top--];
    		if (tot) st[++top] = height[i],cnt[top] = tot;
    		cal[top] = cal[top - 1] + (LL)st[top] * (LL)cnt[top];
    		if (sa[i] > len) ans += cal[top];
    	}
    	top = 0;
    	for (int i = 1; i <= n; i++){
    		if (!height[i]) {top = 0; continue;}
    		int tot = sa[i - 1] > len ? 1 : 0;
    		while (top && st[top] > height[i]) tot += cnt[top--];
    		if (tot) st[++top] = height[i],cnt[top] = tot;
    		cal[top] = cal[top - 1] + (LL)st[top] * (LL)cnt[top];
    		if (sa[i] <= len) ans += cal[top];
    	}
    }
    int main(){
    	scanf("%s",s + 1); len = strlen(s + 1);
    	s[len + 1] = '#';
    	scanf("%s",s + len + 2);
    	n = strlen(s + 1);
    	getSA();
    	solve();
    	cout << ans << endl;
    	return 0;
    }
    
    
  • 相关阅读:
    C/C++,彩色图像小游戏。
    js 调用百度地图,并且定位用户地址,显示省市区街,经纬度
    C/C++ 双精度double 数据相加出错缺陷解释
    辗转相除法求最大公约数,非goto
    如何用 js 获取table 或者其他块状标签的 宽和高
    如何用 ajax 连接mysql数据库,并且获取从中返回的数据。ajax获取从mysql返回的数据。responseXML分别输出不同数据的方法。
    C++ 连接数据库的入口和获取列数、数据
    Win10:如何修改双网卡的优先级?
    如何拉动内需,击中客户深层需求,4个经典案例分析!
    单点登录SSO简介
  • 原文地址:https://www.cnblogs.com/Mychael/p/8469118.html
Copyright © 2020-2023  润新知