• kmp练习


    kmp板子如下, 失配数组不优化的话, $f_i$就表示子串[0...i]前后缀最大匹配长度

    int main() {
    	scanf("%s%s", t, p);
    	int n = strlen(t), m = strlen(p);
    	f[0]=f[1]=0;
    	int j = 0;
    	REP(i,1,m-1) {
    		while (j&&p[i]!=p[j]) j=f[j];
    		if (p[i]==p[j]) ++j;
    		f[i+1] = j;
    	}
    	j = 0;
    	REP(i,0,n-1) {
    		while (j&&p[j]!=t[i]) j=f[j];
    		if (p[j]==t[i]) ++j;
    		if (j==m) printf("%d", i-m+1);
    	}
    }
    

    练习1: hdu5763

    大意: 给定字符串T, 模板串P, 可以将T中与P匹配的子串替换为'*', 求多少种替换方案.

    一个板子题, kmp求出可以替换的位置, 然后dp就好了

    const int N = 1e6+10;
    char t[N], p[N];
    int f[N], ff[N], *dp=ff+1;
    void add(int &a, int b) {a+=b;if (a>=P) a-=P;}
    
    void work() {
    	scanf("%s%s", t, p);
    	int n = strlen(t), m = strlen(p);
    	f[0]=f[1]=0;
    	REP(i,1,m-1) {
    		int j = f[i];
    		while (j&&p[i]!=p[j]) j=f[j];
    		if (p[i]==p[j]) ++j;
    		f[i+1] = j;
    	}
    	int j = 0;
    	dp[-1] = 1;
    	REP(i,0,n-1) {
    		while (j&&p[j]!=t[i]) j=f[j];
    		if (p[j]==t[i]) ++j;
    		dp[i] = 0;
    		if (j==m) add(dp[i],dp[i-m]);
    		add(dp[i],dp[i-1]);
    	}
    	printf("%d
    ",dp[n-1]);
    }
    
    int main() {
    	int t;
    	scanf("%d", &t);
    	REP(i,1,t) printf("Case #%d: ",i),work();
    }
    

    练习2 CF825F

    大意: 给定字符串$s$, 可以将连续$c1$个相同的子串$s1$压缩为|c1|+|s1|, 求压缩若干次后$s$最短长度.

    要用到一个字符串循环节的结论, 假设一个长$n$的字符串$s$, 失配函数为$f$, 则循环节为n-f[n]或n

    const int N = 8e3+10;
    int n;
    char s[N];
    int f[N], g[N][N], ff[N], *dp=ff+1;
    void getFail(char *s) {
    	int m = strlen(s);
    	f[0]=f[1]=0;
    	REP(i,1,m-1) {
    		int j=f[i];
    		while (j&&s[i]!=s[j]) j=f[j];
    		if (s[i]==s[j]) ++j;
    		f[i+1] = j;
    	}
    }
    int calc(int x) {int r=0;while (x) ++r,x/=10;return r;}
    
    int main() {
    	scanf("%s", s);
    	n = strlen(s);
    	REP(i,0,n-1) {
    		getFail(s+i);
    		REP(j,i,n-1) {
    			int len = j-i+1;
    			if (len%(len-f[len])==0) g[i][j]=len-f[len]+calc(len/(len-f[len]));
    			else g[i][j]=len+1;
    		}
    	}
    	dp[-1] = 0;
    	REP(i,0,n-1) {
    		dp[i] = INF;
    		REP(j,0,i) dp[i] = min(dp[i], dp[j-1]+g[j][i]);
    	}
    	printf("%d
    ", dp[n-1]);
    }
    

    练习3 CF494B

    这题想了好长时间, 计数还是不熟练啊

    $dp[i]=j+1+sumlimits_{kle j-1} sum[k]$

    dp[i]是以i为右端点的方案, sum[i]是dp[i]的前缀和, j为上次匹配位置

    int n, m;
    char t[N], p[N];
    int dp[N], s1[N], s2[N], f[N], g[N];
    void add(int &a, int b) {a+=b;if (a>=P)a-=P;}
    
    int main() {
    	scanf("%s%s", t, p);
    	n = strlen(t), m = strlen(p);
    	f[0]=f[1]=0;
    	REP(i,1,m-1) {
    		int j=f[i];
    		while (j&&p[j]!=p[i]) j=f[j];
    		if (p[j]==p[i]) ++j;
    		f[i+1]=j;
    	}
    	int j=0;
    	REP(i,0,n-1) {
    		while (j&&t[i]!=p[j]) j=f[j];
    		if (t[i]==p[j]) ++j;
    		if (j==m) g[i]=1;
    	}
    	REP(i,0,n-1) {
    		if (g[i]) j=i-m+1,dp[i]=s2[j-1],add(dp[i],j+1);
    		else if (i) dp[i] = dp[i-1];
    		if (i) s1[i] = s1[i-1];
    		add(s1[i],dp[i]);
    		if (i) s2[i] = s2[i-1];
    		add(s2[i],s1[i]);
    	}
    	printf("%d
    ",s1[n-1]);
    }
    

    练习4 CF 1015 Bracket Substring

    大意:求长度为2n, 给定括号串$s$, 求所有以$s$为子串的合法括号序列种类数.

    kmp经典套路了, 设$dp[i][j][k]$表示到$i$位时, 已经匹配$j$位, 左右括号差为$k$的方案数

    对于放'(', 若s[j]为'('则匹配位数转移到j+1, 否则沿失配边走. 对于')'的情况同理.

    要注意特殊处理已经转移完毕的情况. 这里可以将失配函数优化一下, 复杂度是$O(omega^2 n^2m), omega$为字符种类数, 不优化的话是$O(omega n^2m^2)$实际上也能跑过.

    const int N = 210;
    int n, m;
    char s[N];
    int dp[N][N][N], f[N];
    
    void add(int &a, int b) {a+=b;if (a>=P)a-=P;}
    
    int main() {
    	scanf("%d%s", &n, s);
    	m = strlen(s);
    	REP(i,1,m-1) {
    		int j = f[i];
    		while (j&&s[i]!=s[j]) j=f[j];
    		if (s[i]==s[j]) ++j;
    		f[i+1]=j;
    	}
    	REP(i,1,m-1) if (s[i]==s[f[i]]) f[i]=f[f[i]];
    	dp[0][0][0]=1;
    	REP(i,1,2*n) REP(j,0,m) REP(k,0,n) if (dp[i-1][j][k]) {
    		int nxt = j;
    		while (nxt&&s[nxt]!='(') nxt=f[nxt];
    		if (s[nxt]=='(') ++nxt;
    		if (j==m) nxt=m;
    		add(dp[i][nxt][k+1],dp[i-1][j][k]);
    		if (!k) continue;
    		nxt = j;
    		while (nxt&&s[nxt]!=')') nxt=f[nxt];
    		if (s[nxt]==')') ++nxt;
    		if (j==m) nxt=m;
    		add(dp[i][nxt][k-1],dp[i-1][j][k]);
    	}
    	printf("%d
    ", dp[2*n][m][0]);
    }
    

    练习5 HDU3689 Infinite monkey theorem 

    跟上题类似, 借助kmp进行转移

    const int N = 1e3+10;
    int n, m;
    double q[N], dp[N][22];
    char s[N];
    int f[N];
    
    
    void work() {
    	REP(i,'a','z') q[i]=0;
    	REP(i,1,n) { 
    		char c;
    		double x;
    		scanf(" %c%lf", &c, &x);
    		q[c] = x;
    	}
    	scanf("%s", s);
    	n = strlen(s);
    	REP(i,1,n-1) {
    		int j=f[i];
    		while (j&&s[j]!=s[i]) j=f[j];
    		if (s[j]==s[i]) ++j;
    		f[i+1]=j;
    	}
    	REP(i,1,n-1) if (s[i]==s[f[i]]) f[i]=f[f[i]];
    	memset(dp,0,sizeof dp);
    	dp[0][0] = 1;
    	REP(i,1,m) REP(j,0,n) {
    		REP(k,'a','z') {
    			int nxt = j;
    			while (nxt&&s[nxt]!=k) nxt=f[nxt];
    			if (s[nxt]==k) ++nxt;
    			if (j==n) nxt=n;
    			dp[i][nxt] += q[k]*dp[i-1][j];
    		}
    	}
    	printf("%.2lf%%
    ", 100*dp[m][n]);
    }
    
    int main() {
    	for (; scanf("%d%d", &n, &m), n||m; ) work();
    }
    

    练习6 CF 432D Prefixes and Suffixes  

    大意: 给定字符串$s$, 求$s$有多少个前缀等于后缀, 并且输出它们的出现次数

    kmp经典题了, 失配函数如果不优化的话, 那么$f_i$就表示子串[0,i-1]中最大的使前缀等于后缀的前缀长度.

    也就是说$s$满足要求的最大前缀长度是$f[n]$, 其余前缀一定在$f[n]$内, 这样就可以迭代求出满足要求的前缀位置.

    再用$dp$求出每个前缀的出现次数即可.

    const int N = 1e6+10;
    int n;
    char s[N];
    int f[N], dp[N], a[N];
    
    
    int main() {
    	scanf("%s", s);
    	n = strlen(s);
    	REP(i,1,n-1) {
    		int j = f[i];
    		while (j&&s[i]!=s[j]) j=f[j];
    		if (s[i]==s[j]) ++j;
    		f[i+1]=j;
    	}
    	for (int nxt=n; nxt; nxt=f[nxt]) a[++*a]=nxt;
    	PER(i,1,n) dp[f[i]]+=++dp[i];
    	printf("%d
    ", *a);
    	PER(i,1,*a) printf("%d %d
    ", a[i], dp[a[i]]);
    }
    

    练习7 CF808G Anthem of Berland

    先预处理了一下第i位是否能匹配到第j位, 最后再dp一遍

    const int N = 1e6+10;
    int n, m;
    char s[N], t[N];
    vector<vector<int> > g;
    int dp[N], mx[N], f[N], ff[N];
    
    
    int main() {
    	scanf("%s%s", s+1, t);
    	n = strlen(s+1), m = strlen(t);
    	if (n<m) return puts("0"),0;
    	REP(i,0,n+10) g.pb(vector<int>());
    	REP(i,0,n+10) REP(j,0,m+10) g[i].pb(0);
    	REP(i,1,m-1) {
    		int j = f[i];
    		while (j&&t[j]!=t[i]) j=f[j];
    		if (t[j]==t[i]) ++j;
    		f[i+1]=j;
    	}
    	memcpy(ff,f,sizeof ff);
    	REP(i,1,m-1) if (t[i]==t[f[i]]) f[i]=f[f[i]];
    	REP(i,0,n) g[i][0] = 1;
    	REP(i,1,n) REP(j,0,m) if (g[i-1][j]) {
    		int nxt = j;
    		while (nxt&&t[nxt]!=s[i]) nxt=f[nxt];
    		if (t[nxt]==s[i]) ++nxt;
    		g[i][nxt] = 1;
    		if (s[i]=='?') g[i][j+1] = 1;
    	}
    	REP(i,1,n) { 
    		if (g[i][m]) { 
    			dp[i] = mx[i-m]+1;
    			for (int j=ff[m]; j; j=ff[j]) {
    				dp[i] = max(dp[i], dp[i-m+j]+1);
    			}
    		}
    		mx[i] = max(dp[i], mx[i-1]);
    	}
    	printf("%d
    ", mx[n]);
    }
    

    看了下别人题解后发现自己想复杂了....只需要判断是否能匹配到$m$位, 暴力判断就好了, 并且复杂度会少一个$omega$,但实际跑了下没差多少.

    const int N = 1e6+10;
    int n, m;
    char s[N], t[N];
    int dp[N], mx[N], f[N];
    
    
    int main() {
    	scanf("%s%s", s+1, t);
    	n = strlen(s+1), m = strlen(t);
    	if (n<m) return puts("0"),0;
    	REP(i,1,m-1) {
    		int j = f[i];
    		while (j&&t[j]!=t[i]) j=f[j];
    		if (t[j]==t[i]) ++j;
    		f[i+1]=j;
    	}
    	REP(i,1,n) { 
    		int ok = 1;
    		REP(j,0,m-1) if (s[i+j-m+1]!='?'&&s[i+j-m+1]!=t[j]) {
    			ok = 0;
    			break;
    		}
    		if (ok) { 
    			dp[i] = mx[i-m]+1;
    			for (int j=f[m]; j; j=f[j]) {
    				dp[i] = max(dp[i], dp[i-m+j]+1);
    			}
    		}
    		mx[i] = max(dp[i], mx[i-1]);
    	}
    	printf("%d
    ", mx[n]);
    }
    
  • 相关阅读:
    spoj LCS2
    spoj SUBLEX
    spoj NSUBSTR
    bzoj 2882: 工艺【SAM】
    poj 3294 Life Forms【SA+二分】
    poj 3415 Common Substrings【SA+单调栈】
    poj 2774 Long Long Message【SA】
    poj 2406 Power Strings【kmp】
    poj 1743 Musical Theme【二分+SA】
    hdu 3622 Bomb Game【二分+2-SAT+tarjan】
  • 原文地址:https://www.cnblogs.com/uid001/p/10536194.html
Copyright © 2020-2023  润新知