JZOJ 3648【GDOI2014】beyond

7 篇文章 0 订阅
4 篇文章 0 订阅

Description:

这里写图片描述
这里写图片描述

Input:

第一行:包含一个整数N。
第二行:包含一个长度为N的字符串,字符串中只包含小写字母。
第三行:包含一个长度为N的字符串,字符串中只包含小写字母。

Output:

输出答案只包含一个数字L,表示圆环最大可能有的格子数。

Sample Input:

输入1:
5
abcdx
cdabz

输入2:
4
abcd
cdab

Sample Output:

输出1:
4

输出2:
4

Data Constraint:

对于20% 的数据,1 <= N <= 5,000
对于50% 的数据,1 <= N <= 600,000
对于100% 的数据,1 <= N <= 2,000,000


题目大意:

给出两个长度为n的字符串a,b,求最大的L,使得a[1..L]和b[1..L]是循环同构的(展开后是一个同一个环)。
1 <= n <= 2000000

题解:

算是exkmp比较裸的题了。
设exkmpA[i]表示a[i..n]与b的lcp。
exkmpB同理。
我们枚举a串的一个位置k,那我们需要在exkmpB[1..exkmpA[k] +1]中找到一个最大的j,使得exkmpB[j] >= k。
exkmpA、exkmpB是很好求的。
关键在于如何找到j。
我们很容易想到线段树维护一个exkmpB的最大值,然后使用线段树二分。
可是不要忘记n是2000000.
O(n logn)只有50分。

由于我们可以从左到右枚举k,我们想象一个数组c,c[i]表示exkmpB[i]是否大于等于当前k,要找到最大的j,就相当于找c[exkmpA[k]+1]往左边(可以是它本身)的第一个1,这个东西我们可以用并查集维护,至于c[i]的修改用前向星解决。

JZOJ的栈没良心,逼得我打了人工栈并查集。

Code:

#include<cstdio>
#include<cstring>
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))
using namespace std;

const int Maxn = 2000005;
int n, ans; char a[Maxn], b[Maxn];
int next[Maxn], exkmp[Maxn];
int f[Maxn], z[Maxn], bz[Maxn];
int final[Maxn], nt[Maxn], to[Maxn], tot;

int find(int x) {
    z[++ z[0]] = x; bz[1] = 0;
    while(z[0]) {
        if(!bz[z[0]]) {
            if(f[z[z[0]]] == z[z[0]]) {
                z[0] --;
            } else {
                bz[z[0]] = 1; z[++ z[0]] = f[z[z[0] - 1]]; bz[z[0]] = 0;
            }
        } else f[z[z[0]]] = f[z[z[0] + 1]], z[0] --; 
    }
    return f[x];
}

void link(int x, int y) {
    nt[++ tot] = final[x], to[tot] = y, final[x] = tot; 
}

void Get_next(char s[]) {
    next[1] = n;

    int a = 1; while(a < n && s[a + 1] == s[a]) a ++;
    next[2] = a - 1, a = 2;

    fo(i, 3, n) {
        int p = a + next[a] - 1, l = next[i - a + 1];
        if(i + l - 1 >= p)  {
            int j = (p - i + 1) > 0 ? (p - i + 1) : 0;
            while(i + j - 1 < n && s[j + 1] == s[i + j]) j ++;
            next[i] = j, a = i;
        } else next[i] = l;
    }
} 

void Get_exkmp(char s[], char t[]) {
    int a = 1; while(a < n && s[a + 1] == t[a + 1]) a ++;
    exkmp[1] = a - 1, a = 1;

    fo(i, 2, n) {
        int p = a + exkmp[a] - 1, l = next[i - a + 1];
        if(i + l - 1 >= p) {
            int j = (p - i + 1) > 0 ? (p - i + 1) : 0;
            while(i + j - 1 < n && j < n && s[i + j] == t[j + 1]) j ++;
            exkmp[i] = j, a = i;
        } else exkmp[i] = l;
        if(i + exkmp[i] - 1 > n)
            exkmp[i] = n - i + 1;
    }
}

void delet(int x) {
    f[x] = find(f[x - 1]);
}

void End() {
    fo(i, 1, n) f[i] = i;
    fo(i, 1, n) {
        int j = find(exkmp[i] + 1);
        if(j) ans = max(ans, i + j - 2);                
        for(int k = final[i]; k; k = nt[k])
            delet(to[k]);
    }
    printf("%d", ans);
}

int main() {
    scanf("%d", &n);
    scanf("%s", a + 1); scanf("%s", b + 1);
    Get_next(a);
    Get_exkmp(b, a);
    fo(i, 1, n) link(exkmp[i] + 1, i);
    Get_next(b);
    Get_exkmp(a, b);
    End();
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值