kuangbin专题KMP & 扩展KMP & Manacher

HDU - 1711 Number Sequence

思路

KMP模板

代码

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N = 1000010;
int s[N], p[N];
int ne[N];

int main() {
    int n, m;
    int T;
    scanf("%d", &T);
    while(T --) {
        scanf("%d %d", &n, &m);

        for(int i = 1; i <= n; i ++) {
            scanf("%d", s + i);
        }

        for(int i = 1; i <= m; i ++) {
            scanf("%d", p + i);
        }

        int ans = -1;

        for (int i = 2, j = 0; i <= m; i ++) {
            while(j && p[j + 1] != p[i])
                j = ne[j];
            if(p[j + 1] == p[i])
                j++;
            ne[i] = j;
        }

        for(int i = 1, j = 0; i <= n; i ++) {
            while(j && p[j + 1] != s[i])
                j = ne[j];
            if(p[j + 1] == s[i])
                j++;
            if(j == m) {
                ans = i - m + 1;
                break;
            }
        }

        printf("%d\n", ans);
    }

    return 0;
}

HDU - 1686 Oulipo

思路

求模式串在匹配串出现的次数
还是KMP模板

代码

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N = 1000010;
char s[N], p[N];
int ne[N];

int main() {

    int T;
    scanf("%d", &T);
    while(T --) {
        scanf("%s", p + 1);
        scanf("%s", s + 1);

        int n = strlen(s + 1);
        int m = strlen(p + 1);

        int ans = 0;

        for (int i = 2, j = 0; i <= m; i ++) {
            while(j && p[j + 1] != p[i])
                j = ne[j];
            if(p[j + 1] == p[i])
                j++;
            ne[i] = j;
        }

        for(int i = 1, j = 0; i <= n; i ++) {
            while(j && p[j + 1] != s[i])
                j = ne[j];
            if(p[j + 1] == s[i])
                j++;
            if(j == m) {
                ans++;
                j = ne[j];
            }
        }

        printf("%d\n", ans);
    }

    return 0;
}

HDU - 2087剪花布条

题意

问能从匹配串裁剪多少个模式串

思路

还还还是模板题
只需要当找到一个匹配的,将j = 0而不是j=ne[i]

代码

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N = 1010;
char s[N], p[N];
int ne[N];

int main() {

    while(~scanf("%s", s + 1)) {
        if(s[1] == '#')
            break;
        scanf("%s", p + 1);

        int n = strlen(s + 1);
        int m = strlen(p + 1);

        int ans = 0;

        for (int i = 2, j = 0; i <= m; i ++) {
            while(j && p[j + 1] != p[i])
                j = ne[j];
            if(p[j + 1] == p[i])
                j++;
            ne[i] = j;
        }

        for(int i = 1, j = 0; i <= n; i ++) {
            while(j && p[j + 1] != s[i])
                j = ne[j];
            if(p[j + 1] == s[i])
                j++;
            if(j == m) {
                ans++;
                j = 0;
            }
        }

        printf("%d\n", ans);
    }

    return 0;
}

HDU - 3746 Cyclic Nacklace

题意

求加多少字符能将其字符串变为 至少循环两次 的字符串
例如:
1.abcab
需要加一个c,-> abcabc

2.abcd
需要加abcd,-> abcdabcd

思路

最小循环节问题,求ne数组即可
令字符串的长度为n
最小循环节len = n - ne[n]
则分情况判断即可
若要添加的情况,则add = len - (ne[n] % len)
若不用添加的情况, add = 0

代码

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;

const int N = 100010;

char p[N];
int ne[N];

int main() {
    int T;
    scanf("%d", &T);
    while(T --) {
        scanf("%s", p + 1);
        int n = strlen(p + 1);
        // 求ne数组
        for (int i = 2, j = 0; i <= n; i ++) {
            while(j && p[j + 1] != p[i])
                j = ne[j];
            if(p[j + 1] == p[i])
                j++;
            ne[i] = j;
        }

        // 最小循环节长度
        int len = n - ne[n];
        // 需要添加的字符个数
        int ans;
        if(len != n && n % len == 0) {
            ans = 0;
        }
        else {
            ans = len - (ne[n] % len);
        }

        printf("%d\n", ans);
    }

    return 0;
}

HDU - 1358 Period

思路

和上题类似
记住公式(模拟一下就知道)
最小前缀循环节: len = i - ne[i]
具有完整的循环节判断:i % (len) == 0
并注意求循环次数时必须满足:i / (i - ne[i]) > 1,即循环次数不能为1

代码

#include<iostream>
#include<cstring>
#include<algorithm>

using namespace std;
const int N = 1000010;

char p[N];
int ne[N];

int main() {
    int id = 0;
    int n;
    while(~scanf("%d", &n) && n){
        printf("Test case #%d\n", ++ id);

        scanf("%s", p + 1);
        for (int i = 2, j = 0; i <= n; i ++) {
            while(j && p[j + 1] != p[i])
                j = ne[j];
            if(p[j + 1] == p[i])
                j++;
            ne[i] = j;
        }

        for (int i = 2; i <= n; i ++) { 
            if(i % (i - ne[i]) == 0 && i / (i - ne[i]) > 1) {
                printf("%d %d\n", i, i / (i - ne[i]));
            }
        }

        puts("");
    }

    return 0;
}

POJ - 2406 Power Strings

思路

还是最小循环节问题

代码

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>

using namespace std;

const int N = 1000010;

char p[N];
int ne[N];

int main() {
    while(~scanf("%s", p + 1) && p[1] != '.') {
        int n = strlen(p + 1);
        for (int i = 2, j = 0; i <= n; i ++) {
            while(j && p[j + 1] != p[i])
                j = ne[j];
            if(p[j + 1] == p[i])
                j++;
            ne[i] = j;
        }

        int len = (n - ne[n]);
        if(n % len == 0)
            printf("%d\n", n / len);
        else
            printf("1\n");
    }

    return 0;
}

POJ - 2752 Seek the Name, Seek the Fame

思路

运用ne[i]数组的性质
它记录的就是当前1~i序列 的最大相同前后缀的长度

代码

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>

using namespace std;

const int N = 400010;

char p[N];
int ne[N];
int stk[N];
int top;

int main() {
    while(~scanf("%s", p + 1)) {
        top = -1;
        int n = strlen(p + 1);
        for (int i = 2, j = 0; i <= n; i ++) {
            while(j && p[j + 1] != p[i])
                j = ne[j];
            if(p[j + 1] == p[i])
                j++;
            ne[i] = j;
        }

        stk[++top] = n;
        for (int i = ne[n]; i; i = ne[i]) {
            stk[++top] = i;
        }

        while(top != -1) {
            printf("%d ", stk[top--]);
        }
        puts("");
    }

    return 0;
}

题意

找公共连续子串

思路

暴力即可,数据过小,不用kmp加速都行
暴力枚举第一个串的所有子串,check其他串是否能匹配成功,若可以,则更新答案

代码

#include <cstdio>
#include <cstring>
using namespace std;
const int N = 65;
char s[11][N], p[N], ans[N];
int n, m, mx, t, k, ne[N];
void getNext() {
	ne[1] = 0;
	for (int i = 2, j = 0; i <= n; i++) {
		while (j && p[i] != p[j + 1]) j = ne[j];
		if (p[i] == p[j + 1]) j++;
		ne[i] = j;
	}
}
bool kmp(int m) {
    // kmp匹配加速
    getNext();
    for (int i = 1, j = 0; i <= 60; i++) {
		while (j && s[m][i] != p[j + 1]) j = ne[j];
		if (s[m][i] == p[j + 1]) j++;
		if (j == n) return true;//匹配成功 
	}
	return false;
}

// 对所有字符串进行匹配, 是否都存在模式串
bool ok() {
	for (int i = 1; i <= m; i++) {
		if (!kmp(i)) return false;
	}
	return true;
} 
int main() {
	scanf("%d", &t);
	while (t--) {
		mx = 0;
		scanf("%d", &m);
		for (int i = 1; i <= m; i++) scanf("%s", s[i] + 1); 
		//枚举所有子串
		for (int i = 1; i <= 60; i++) {
			for (int j = i; j <= 60; j++) {
				for (int k = i; k <= j; k++) p[k - i + 1] = s[1][k]; 
				n = j - i + 1; 
                // 因为之前p数组是复用的, 这里加个'\0',代表字符串到这结束, 防止后面的字符串操作出错
                p[n + 1] = '\0';
				if (ok()) {
					if (n > mx) { mx = n; memcpy(ans + 1, p + 1, sizeof(p));}
                    // 保证字典序小
					else if(n == mx && strcmp(ans + 1, p + 1) > 0) memcpy(ans + 1, p + 1, sizeof(p));
				}  
			}
		} 
		if (mx < 3) printf("no significant commonalities\n");
		else printf("%s\n", ans + 1);
	}
	return 0;
}

POJ - 2185 Milking Grid

题意

求最小循环矩阵

思路

还是最小循环节问题。
只不过一维变为二维

  1. 求每行的最小循环节,搞个LCM得ans1
  2. 求每列的最小循环节,搞个LCM得ans2
  3. 答案 = ans1 * ans2
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>

using namespace std;

const int N = 10010;
int n, m;
int ne[N];
char str[N][N];

int gcd(int a, int b) {
    return b ? gcd(b, a % b) : a;
}

int lcm(int a, int b) {
    return a / gcd(a, b) * b;
}

int getr(int pos) {
    memset(ne, 0, sizeof ne);
    for (int i = 2, j = 0; i <= m; i ++) {
        while(j && str[pos][i] != str[pos][j + 1])
            j = ne[j];
        if(str[pos][i] == str[pos][j + 1])
            j++;
        ne[i] = j;
    }

    return m - ne[m];
}

int getl(int pos) {
    memset(ne, 0, sizeof ne);
    for (int i = 2, j = 0; i <= n; i ++) {
        while(j && str[i][pos] != str[j + 1][pos])
            j = ne[j];
        if(str[i][pos] == str[j + 1][pos])
            j++;
        ne[i] = j;
    }

    return n - ne[n];
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++)
        scanf("%s", str[i] + 1);

    int ans1 = 1;
    for (int i = 1; i <= n; i ++) {
        ans1 = lcm(ans1, getr(i));
    }

    if(ans1 > m)
        ans1 = m;

    int ans2 = 1;
    for (int i = 1; i <= m; i ++)
        ans2 = lcm(ans2, getl(i));
    
    if(ans2 > n)
        ans2 = n;
    
    printf("%d\n", ans1 * ans2);
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值