zoukankan      html  css  js  c++  java
  • 字符串板子

    只是板子和性质, 主要用于复习, 想要学习的话, 去找其他帖子
    以前都是懂原理的, 好久不写就忘的差不多了, 这东西会用就好, 懂原理更好

    kmp及其扩展

    kmp

    f[i]表示 模板串t的以第i个字符结尾的后缀字符串 与 模板串前缀字符串 匹配的最大长度

    n-f[n] 为字符串的最小循环节长度(忽略最后没显示的) ababa 5-3=2,故循环节(ab)长度为2

    aaabaa 最小循环节为aaab,但还有aaaba、aaabaa

    对于完美覆盖,最小循环节完美覆盖字符串,则其他循环节必定是最短循环节的倍数

    kmp扩展

    extend[i]表示 匹配串s的以第i个字符结尾的后缀字符串 与 模板串t前缀字符串 匹配的最大长度

    一般用于查找匹配串中有多少个模板串

    代码

    #include<bits/stdc++.h>
    using namespace std;
    const int LenT = 1e5 + 5, LenS = 1e6 + 5;
    char t[LenT], s[LenS];
    int lens, lent;
    int f[LenT], extend[LenS];
    int cnt;
    //n-f[n] 为字符串的最小循环节长度(忽略最后没显示的) ababa 5-3=2,故循环节(ab)长度为2
    //aaabaa 最小循环节为aaab,但还有aaaba、aaabaa
    //对于完美覆盖,最小循环节完美覆盖字符串,则其他循环节必定是最短循环节的倍数
    void KMP()
    {
    	f[1] = 0;
    	for (int i = 2, j = 0; i <= lent; ++i)
    	{
    		while (j > 0 && t[i] != t[j + 1]) j = f[j];
    		if (t[i] == t[j + 1]) ++j;
    		f[i] = j;
    	}
    }
    
    void ext_KMP()
    {
    	extend[1] = 0;
    	for (int i = 1, j = 0; i <= lens; ++i)
    	{
    		while (j > 0 && (j == lent || s[i] != t[j + 1]))
    			j = f[j];
    		if (s[i] == t[j + 1])
    			++j;
    		extend[i] = j;
    		if (j == lent)
    			++cnt;
    	}
    }
    
    int main()
    {
    	scanf("%s%s", t+1, s+1);
    	lens = strlen(s+1);
    	lent = strlen(t+1);
    	KMP();
    	ext_KMP();
    	printf("%d", cnt);
    	return 0;
    }
    

    Trie数

    就是简单的把给的所有串全部放到树中, 但是空间开销较大, 为 (字符种类数^{maxlen}) 或者 maxlen * 字符种类数

    通常用于寻找匹配串中有多少模板串

    代码

    #include<bits/stdc++.h>
    using namespace std;
    const int SIZE=100+5;
    int trie[SIZE][26],tot=1,ended[SIZE];
    void insert(char *str)
    {
        int len=strlen(str),p=1;
        for(int k=0;k<len;++k)
        {
            int ch=str[k]-'a';
            if(trie[p][ch]==0)
                trie[p][ch]=++tot;
            p=trie[p][ch];
        }
        ended[p]=true;
    }
    bool search(char *str)
    {
        int len=strlen(str),p=1;
        for(int k=0;k<len;++k)
        {
            p=trie[p][str[k]-'a'];
            if(p==0)
                return 0; 
        }
        //如果end[p]=false,则相当于有 cat 查 ca 尽管存在 ca 单词 但并没有 ca
        return ended[p];
    }
    int main()
    {
        return 0;
    } 
    

    马拉车

    寻找最大回文串

    核心思想

    代码

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N = 110005;
    
    int pArr[N << 1];
    char s[N], chaArr[N << 1];
    
    int maxLcsplength() {
    	register int len = 0;
    	chaArr[len++] = '$', chaArr[len++] = '#';
    
    	for (register int i = 0; s[i]; ++i) chaArr[len++] = s[i], chaArr[len++] = '#';
    	chaArr[len] = '';
    	
    	register int R = -1, C = -1, maxN = 0;
    	for (register int i = 0; i < len; ++i) {
    		pArr[i] = R > i ? min(R - i, pArr[(C << 1) - i]) : 1;
    		while (chaArr[i + pArr[i]] == chaArr[i - pArr[i]]) ++pArr[i];
    		if (i + pArr[i] > R) R = i + pArr[i], C = i;
    		maxN = max(maxN, pArr[i]);
    	}
    	return maxN - 1;
    }
    
    int main() {
    	while (gets(s)) {
    		printf("%d
    ", maxLcsplength());
    		getchar();
    	}
    	return 0;
    }
    

    字符串环的最大最小表示

    代码

    #include<bits/stdc++.h>
    using namespace std;
    
    const int N = 10000;
    
    char str[N << 1];
    int len;
    
    int getMin(char s[]) {
    	int i = 0, j = 1, k = 0, t;
    	while (i < len && j < len && k < len) {
    		t = s[(i + k) % len] - s[(j + k) % len];
    		if (t == 0) ++k;
    		else {
    			if (t > 0) i += k + 1;
    			else j += k + 1;
    			if (i == j) ++j;
    			k = 0;
    		}
    	}
    	return i > j ? j : i;
    }
     
    int getMax(char s[]) {
    	int i = 0, j = 1, k = 0, t;
    	while (i < len && j < len && k < len) {
    		t = s[(i + k) % len] - s[(j + k) % len];
    		if (t == 0) ++k;
    		else {
    			if (t < 0) i += k + 1;
    			else j += k + 1;
    			if (i == j) ++j;
    			k = 0;
    		}
    	}
    	return i > j ? j : i;
    }
    
    int main()
    {
        scanf("%s", str + 1);
        len = strlen(str + 1);
        strncpy(str + len + 1, str + 1, len-1);
    
        char maxa[N], mina[N];
        strncpy(maxa + 1, str + getMax(str), len);
        strncpy(mina + 1, str + getMin(str), len);
    
        printf("%s
    %s", maxa + 1, mina + 1);
        return 0;
    }
    

    AC自动机

    Trie树的思想, 就是多了个失配指针

    代码

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    
    #define RE register
    #define FOR(i,a,b) for(RE int i=a;i<=b;++i)
    #define ROF(i,a,b) for(int i=a;i>=b;--i)
    #define ll long long 
    #define sc(x) scanf("%d",&x)
    #define scc(x,y) scanf("%d%d",&x,&y)
    #define sccc(x,y,z) scanf("%d%d%d",&x,&y,&z)
    #define ss(s) scanf("%s",s)
    
    using namespace std;
    
    const int MAXN = 1000005;
    const int MAXM = 51;
    const int NUM = 10001;
    const int C = 'a';
    const int M = 26;
    
    struct AC {
        static const int N = 1e5 + 5, M = 26, C = 'a'; //字符串总长度, 字符范围
    
        int trie[N][M], cnt[N], fail[N];
        vector<VI> idx; //记录节点结尾的字符串id
        int q[N], tot;
    
        void init() {
            rep (i, 0, M - 1) trie[0][i] = 0;
            tot = 0; 
            idx.resize(1, VI());
        }
    
        int newnode() {
            cnt[++tot] = 0; fail[tot] = 0;
            rep (i, 0, M - 1) trie[tot][i] = 0;
            idx.pb(VI());
            return tot;
        }
    
        void insert(char* s, int id) {
            int p = 0;
            for (int i = 0; s[i]; ++i) {
                int ch = s[i] - C;
                if (!trie[p][ch]) trie[p][ch] = newnode();
                p = trie[p][ch];
            }
            ++cnt[p];
            idx[p].pb(id);
        }
    
        void build() {
            int head = 0, tail = -1;
            rep (i, 0, M - 1) if (trie[0][i]) q[++tail] = trie[0][i];
            while (head <= tail) {
                int p = q[head++];
                rep (i, 0, M - 1)
                    if (trie[p][i])
                        fail[trie[p][i]] = trie[fail[p]][i], q[++tail] = trie[p][i];
                    else trie[p][i] = trie[fail[p]][i];
            }
        }
    
        int query(char* s) {
            set<int> vis;
            int p = 0, res = 0;
            for (int i = 0; s[i]; ++i) {
                p = trie[p][s[i] - C];
                for (int tmp = p; tmp && !vis.count(tmp); tmp = fail[tmp])
                    res += cnt[tmp], vis.insert(tmp);
            }
            return res;
        }
    } a;
    
    int _, n;
    char s[MAXN];
    
    int main() {
    	for (sc(_); _; --_) {
    		sc(n);
    		FOR(i, 1, n)
    			ss(s), a.insert(s, i);
    		a.build();
    		ss(s);
    		printf("%d
    ", a.query(s));
    		a.init();
    	}
    	return 0;
    }
    

    后缀数组(nlogn)

    太菜了, 只会倍增的写法

    sa和rk(rank)数组互逆, sa[i]表示以i为开头的原串的后缀串的排名, rk[i]表示原串后缀串第i名在原串中的位置

    后缀数组sa,rk有了没啥用, 主要是得到 heigh数组, hi表示 rk[i] 和 rk[i - 1]的后缀串的最大前缀长度

    然后就是倍增rmq

    倍增rmq

    有的题直接给你一堆字符串, 可以直接sort(), 然后预处理, 按照后缀数组的rmq去写

    代码

    #include <bits/stdc++.h>
    #define RE register
    #define FOR(i,a,b) for(RE int i=a;i<=b;++i)
    #define ROF(i,a,b) for(RE int i=a;i>=b;--i)
    using namespace std;
    const int N = 30000 + 9;
    char str[N];
    int sa[N], rk[N], tp[N], tax[N], lcp[N], len, f[N][30], M;
    
    inline void sort() {
    	memset(tax, 0, (M + 1) * sizeof(int));
    	//FOR(i, 0, M)tax[i] = 0;
    	FOR(i, 1, len) ++tax[rk[i]];
    	FOR(i, 1, M) tax[i] += tax[i - 1];
    	ROF(i, len, 1) sa[tax[rk[tp[i]]]--] = tp[i];
    }
    
    void getH() {
    	int j, k = 0;
    	FOR(i, 1, len) {
    		if (k) --k;
    		j = sa[rk[i] - 1];
    		while (str[i + k] == str[j + k])++k;
    		lcp[rk[i]] = k;
    	}
    }
    
    
    void SuffixSort() {
    	M = 200;
    	FOR(i, 1, len) rk[i] = str[i], tp[i] = i;
    	sort();
    	for (RE int w = 1, p = 0; p < len; w <<= 1, M = p) {
    		p = 0;
    		FOR(i, 1, w)tp[++p] = len - w + i;
    		FOR(i, 1, len)if (sa[i] > w)tp[++p] = sa[i] - w;
    		sort();
    		swap(tp, rk);
    		rk[sa[1]] = p = 1;
    		FOR(i, 2, len)
    			rk[sa[i]] = (tp[sa[i - 1]] == tp[sa[i]] 
    				&& tp[sa[i - 1] + w] == tp[sa[i] + w]) ? p : ++p;
    	}
    	getH();
    }
    
    void rmq_init() {
    	memset(f, 63, sizeof f);
    	FOR(i, 0, len-1)f[i][0] = lcp[i + 1];
    	for (int j = 1,mj=2; mj <= len; ++j,mj<<=1)
    			for (int i = 1; i + mj <= len; ++i)
    				f[i][j] = min(f[i][j - 1], f[i + (mj>>1)][j - 1]);
    }
    
    int rmq_query(int l, int r) {
    	if (l<1 || r>len)return 0;
    	l = rk[l], r = rk[r];
    	if (l > r)swap(l, r);
    	int k =r-l<2?0:ceil(log2(r-l))-1; 
    	return min(f[l][k], f[r - (1 << k)][k]);
    }
    
    int main() {
        scanf("%s", str + 1);
        len = strlen(str + 1);
        SuffixSort();
        getH();
        FOR(i, 1, len)
            printf("%d ", sa[i]-1);
        puts("");
        FOR(i,1,len)
            printf("%d ",lcp[i]);
        return 0;
    }
    

    后缀自动机

    仿着AC自动机有个失配指针, 不过是endpos等价类的父类

    这东西是工具, 要会树上dp才行

    //1.如果两个子串�? endpos 相同,则其中子串一个必然为另一个的后缀

    //2.对于任意两个子串 t �? p (len_t<=len_p)要么 endpos(p)∈endpos(t) ,要�? endpos(t)∩endpos(p)= �?

    //3.对于 endposendpos 相同的子串,我们将它们归为一�? endposendpos 等价类。对于任意一�? endposendpos
    //等价类,将包含在其中的所有子串依长度从大到小排序,则每一个子串的长度均为上一个子串的长度�? 11 ,且�?
    //上一个子串的后缀(简单来说,一�? endposendpos 等价类内的串的长度连续)

    //4.endpos 等价类个数的级别�? O(n)

    //5.一个类 aa 中,有最长的子串,也有最短的子串,我们称最长子串的长度�? len(a)len(a) ,最短子�?
    //长度�? minlen(a) 。对于存在父子关系的两个类,�? fa(a) 表示�? a 的父亲(也是一个类)。则�? l
    //en(fa(a))+1=minlen(a)

    //6.后缀自动机的边数�? O(n)O(n)

    /////////////////后缀自动机的一般性可以让我们处理的问题有:判断子串,计算不同子串个数等�?

    代码

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N = 4e5 + 10;
    
    struct node
    {
        int fa, len;
        map<char, int> nxt;
    }tr[N];
    
    int sz, las;
    
    void init()
    {
        sz = las = 1;
        tr[1].len = tr[1].fa = 0;
        map<char, int>().swap(tr[1].nxt);
    }
    
    void add(char c)
    {
        int p = las; int cur = las = ++sz;
        tr[cur].len = tr[p].len + 1;
        for (; p && !tr[p].nxt.count(c); p = tr[p].fa)
            tr[p].nxt[c] = cur;
        if (p == 0) { tr[cur].fa = 1; return; }
        int q = tr[p].nxt[c];
        if (tr[q].len == tr[p].len + 1) { tr[cur].fa = q; return; }
        int nq = ++sz; tr[nq] = tr[q];
        tr[nq].len = tr[p].len + 1;
        for (; p && tr[p].nxt[c] == q; p = tr[p].fa)
            tr[p].nxt[c] = nq;
        tr[q].fa = tr[cur].fa = nq;
    }
    
    int dp[N], n;
    char s[N >> 1];
    
    void solve()
    {
        int lenx = 1, p = 1, tmp =  0;
        for(int& i = lenx; s[i]; ++i)
        {
            if (tr[p].nxt.count(s[i])) p = tr[p].nxt[s[i]], ++tmp; 
            else 
            {
                while (!tr[p].nxt.count(s[i]) && p) p = tr[p].fa;
                if(p == 0) p = 1, tmp = 0;
                else tmp = tr[p].len + 1, p = tr[p].nxt[s[i]]; //注意顺序
            }
            if(!tmp) { puts("-1"); return; }
            dp[i] = dp[i - tmp] + 1;
        }--lenx;
        printf("%d
    ", dp[lenx]);
    }
    
    int main()
    {
        scanf("%s%d", s + 1, &n); init();
        for (int i = 1; s[i]; ++i) add(s[i]);
        for (int i = 1; i <= n; ++i)
            scanf("%s", s + 1), solve();
        return 0;
    }
    
  • 相关阅读:
    CURL POST提交json类型字符串数据和伪造IP和来源
    windows下nginx的配置
    常用JS兼容问题工具
    无限级分类--Array写法
    JS获取对象指定属性在样式中的信息
    解决IE和Firefox获取来源网址Referer的JS方法
    异步轮询函数
    响应式布局--特殊设备检测
    jQuery Validate校验
    [LeetCode#124]Binary Tree Maximum Path Sum
  • 原文地址:https://www.cnblogs.com/2aptx4869/p/13154795.html
Copyright © 2011-2022 走看看