KMP相关题目

\(KMP\)专题

[POI2006]OKR-Periods of Words

解题思路:

求成为两倍前缀的前缀长度之和,那么我们就用 \(F(nxt)\) 数组的性质:

前缀 \(i\) 的长度为 \(F[i]\) 的前缀和后缀是相等的

说明,如果有 \(i\) 一个公共后缀长度为 \(j\) ,那么这个前缀 \(i\) 就有一个周期为 \(i-j\).

那么我们就有以下解法:

先求出 \(F\) 数组,对于每个前缀 \(i\) ,令 \(j=i\), 然后在 \(j>0\) 的情况下,令 \(j=F[j]\), 最小的 \(j\) 就是答案,为 \((i-j)\) 。

答案就是加和。

#include<bits/stdc++.h>
using namespace std;
#define int long long 
const int N=1e6+5;
char a[N];
int n,cnt=0,f[N];
signed main(){
    cin>>n; scanf("%s",a);
    f[0]=f[1]=0;int j=0;
    for(int i=1;i<n;i++){//求解next
        while(j&&(a[i]!=a[j])) j=f[j];
        j+=(a[i]==a[j]);f[i+1]=j;
    }
    for(int i=1;i<=n;i++){
        j=i;
        while(f[j]) j=f[j];
        if(f[i]!=0) f[i]=j;//记忆化
        cnt+=i-j;
    }
    printf("%lld\n",cnt);
    system("pause");
    return 0;
}

P3454 [POI2007]OSI-Axes of Symmetry

解题思路:

这道题其实题意很简单:给定一个多边形,求对称轴的数量。

思考一下,暴力枚举肯定不可行,我们就把问题转化一下:

将多边形顺时针转一圈,将角和边的值连在一起就成了环

假如有一个边长为 \(1\) 的等边三角形,则它的角和边序列应该是: \(1,60°,1,60°,1,60°\),围成一个含有 \(2n\) 个元素的环(角为环上的边,边为环上的结点)。

将 \(1,60°\) 记为 \(a,b\) ,则环为:

KMP相关题目

而对称轴会把这些点分成两部分,且两部分完全一样,对应到序列上就是:断开环上的某一条边,且连成的序列是回文的

对于环的处理:一种常见的处理方法是选择任意一个位置断开,将序列复制成为2倍长度的链。

然后就可以在这条长度为 \(4n\) 的链上找长度为 \(2n\) 的回文串。

怎么找回文串?用 \(KMP\) 是个好方法:

先选择任意一个位置断开,记该序列为 \(S_0\) ,再复制一遍得到序列 \(S\) ,将 \(S_0\)反过来得到串 \(T\) ,求 \(S\) 中有多少个位置和 \(T\) 匹配即可.

同时运用了计算几何相关知识。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
inline void qread(int &x) {
	x=0;
	int sign=1;
	char c=getchar();
	while(c<'0'||c>'9') {
		if(c=='-') sign=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9') {
		x=x*10+c-'0';
		c=getchar();
	}
	x=x*sign;
}

int n;
int T;
struct Point {//点 
	long long x;
	long long y;
	Point() {

	}
	Point(long long xx,long long yy) {
		x=xx;
		y=yy;
	}
	friend Point operator +(Point a,Point b) {
		return Point(a.x+b.x,a.y+b.y);
	}
	friend Point operator -(Point a,Point b) {
		return Point(a.x-b.x,a.y-b.y);
	}
} a[maxn];
long long dot(Point a,Point b) {//点积 
	return a.x*b.x+a.y*b.y;
}
long long cross(Point a,Point b) {//叉积 
	return a.x*b.y-a.y*b.x;
}

long long dist(Point a,Point b) {//计算两点间距离 
	Point v=a-b;
	return dot(v,v);
}

long long work_edge(int x) {//逐一处理多边形的边,注意编号为n的点下一个点是1 
	int y=x+1;
	if(y>n) y=1;
	return dist(a[x],a[y]);
}
long long work_ang(int x) {//处理角,同样注意编号为n的点下一个点是1 
	int y=x+1,z=x+2;
	if(y>n) y=y%n;
	if(z>n) z=z%n;
	return cross(a[y]-a[x],a[z]-a[y]);
}
long long edge[maxn];
long long ang[maxn];
long long tmp[maxn];
int s[maxn*4];
int t[maxn*2];

int nxt[maxn*4];
int f[maxn*4];
int KMP(int *a,int n,int *b,int m) {//KMP模板 
	nxt[1]=0;
	for(int i=2,j=0; i<=n; i++) {
		while(j>0&&a[i]!=a[j+1]) j=nxt[j];
		if(a[i]==a[j+1]) j++;
		nxt[i]=j;
	}
	for(int i=2,j=0; i<=m; i++) {
		while(j>0&&b[i]!=a[j+1]) j=nxt[j];
		if(b[i]==a[j+1]) j++;
		f[i]=j;
	}
	int cnt=0;
	for(int i=1; i<=m; i++) {
		if(f[i]==n) cnt++;
	}
	return cnt;
}
int main() {
	int x,y;
	qread(T);
	while(T--) {
		qread(n);
		for(int i=1; i<=n; i++) {
			qread(x);
			qread(y);
			a[i].x=x;
			a[i].y=y;
		}
		for(int i=1; i<=n; i++) {
			edge[i]=work_edge(i);
			ang[i]=work_ang(i);
		}
		int newn=0;
		int newm=0;
		for(int i=1; i<=n; i++) {//由于计算的角是第i与i+1条边之间的夹角,所以先加入边,再加入角 
			s[++newn]=edge[i];
			s[++newn]=ang[i];
		}
		for(int i=1; i<=n; i++) {
			s[++newn]=edge[i];
			s[++newn]=ang[i];
		}
		for(int i=n*2; i>=1; i--) {
			t[++newm]=s[i];
		}
		printf("%d\n",KMP(t,newm,s,newn));
	}
}
上一篇:学习笔记——KMP算法


下一篇:kmp算法