• 2020寒假集训总结


    在寒假不长又不短的7天(饭菜吃着我难受),我学到了很多东西。好记性不如烂笔头,现在我将其写下,以供后来参考。

    day1 hash

    第一天学了hash,这是一个非常好用的简单数据结构(但又不是数据结构),我现在总结以下几点:

    1.hash的原理

    hash本质上是将一个字符串(这本来要用一个数组开)转化为一个整数(8bits),可以极大地节省空间。并且由于hash可以将一个串变成整数,自然也有了整体打包的功能。而hash有两种写法(孔乙己???):

    ①对于时时刻刻要用各个地方的hash:

    hash[i]=(hash[i-1]*p%mod+s[i]-'a'+1)%mod;
    

    这里的p和mod是两个大质数,为的是防止重复(准确说是减少重复)。

    ②对于特殊的hash(这是我的独家秘笈哦)

    让我们看这样的一道题

    题目描述
    给出一个只包含小写字母字符串,要求你将它划分成尽可能多的小块,使得这些小块构成回文串。
    
    例如:对于字符串abcab,将他分成ab  c  ab或者abcab就是构成回文串的划分方法,abc   ab则不是。
    
    输入格式
    第一行输入一个整数T表示数据组数。
    
    接下来的T行,每行输入一个字符串,代表你需要处理的字符串,保证该字符串只包含小写字母。
    
    输出格式
    
    输出T行,对于每个输入的字符串,输出一行包含一个整数x,表示该字符串最多能分解成x个小块,使得这些小块构成回文串。
    

    这里可以想到的是整体法,因为这道题是将一整个字符串当成一个整体来做回文。

    但是我们可以发现一个性质:当外面的相同后就可以丢去,只关心里面的!!!

    所以有两种做法(孔乙己???):

    1. 用KMP进行多次匹配(前后缀嘛),但是如果数据是 aaaaaaaaaaaaaaaaaaaaaaa……的话,n²的时间复杂度。

    2. 用hash。

    ①可以将hash的每个只求出来,然后用子串公式:

    hashhh[i][j]=(hash[j]-hash[i-1]*power(p,j-i+1,mod)%mod+mod)%mod;
    

    表示子串i到j的hash值(包含i,j);

    ②但我们发现了一个特殊的性质:这种题我们只需要将那一个连续串的hash求出来,并且不需要用到已经查询过的hash,于是我们便可以有不回头hash算法(下面贴我的代码,这种方法只是原创,并且特别利用了hash的原理)

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    using namespace std;
    const long long MM=1e6+20;
    const long long p=786433;
    const long long mod1=1e9+9;
    int t; 
    char txt[MM];
    
    long long power(long long a,long long b,long long mm){
    	long long ans=1%mm;
    	for(;b;b>>=1){
    		if(b&1) ans=ans*a%mm;
    		a=a*a%mm;
    	}
    	return ans;
    }
    
    inline int read(){
       int s=0,w=1;
       char ch=getchar();
       while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
       while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
       return s*w;
    }
    
    void judge(){
    	int start1=0,start2=strlen(txt)-1,end1=0,end2=strlen(txt)-1;
    	int one=0,two=0,ans=0;
    	while(end1<start2){
    		one=(one*p)%mod1+txt[end1]-'a'+1;
    		two=(two+((txt[start2]-'a'+1)*power(p,end2-start2,mod1))%mod1+mod1)%mod1;
    		if(one==two){
    		end1++;start1=end1;
    		one=two=0;
    		start2--;end2=start2;
    		ans+=2;
    		}
    		else end1++,start2--;
    	}
    	long long l=strlen(txt);
    	if(end1!=start1) if(l%2==0)ans++;
    	if(l%2!=0) ans++;
    	cout<<ans<<endl;
    }
    
    int main(){
    	t=read();
    	while(t--){
    		scanf("%s",txt);
    		judge();
    	}
    	return 0;
    } 
    

    2.hash的注意事项

    记得开long long!!!!!!!(血的教训)

    时时刻刻记得mod模一下,不要怕时间复杂度过高

    day2 KMP

    KMP不难,主要是next后缀数组的运用与理解,现在我贴出代码并进行解释

    void pre_next(){
    	int s1=0,s2=-1;
        next[0]=-1;
        while(s1<lenth){
        	if(s2==-1||b[s1]==b[s2]) next[++s1]=++s2;
            else s2=next[s2];
        }
    }
    

    解释的话,请看这篇博客

    循环节相关的知识的话……

    看这篇博客吧(自己网上搜)

    然后附上几道题

    理解kmp算法:poj2752+ poj2406+ poj1961+

    常规kmp算法练习:poj3461+ poj2185(很妙,值得再看)

    day3 manacher

    没啥说的,注意一下代码细节就对了

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    #include<vector>
    using namespace std;
    
    int manacher(string s){
    	string pre="$#";
    	for(int i=0;i<s.size();i++){
    		pre+=s[i];pre+="#";
    	}
    	vector<int> p(pre.size(),0);
    	
    	int mid=0,rightmax=0,lenmax=0;
    	
    	for(int i=1;i<pre.size();i++){
    		if(rightmax>i) p[i]=min(p[2*mid-i],rightmax-i);
    		else p[i]=1;
    		while(pre[i+p[i]]==pre[i-p[i]]) p[i]++;
    		
    		if(i+p[i]>rightmax) rightmax=i+p[i],mid=i;
    		
    		if(p[i]>lenmax) lenmax=p[i];
    	}
    	return lenmax-1; 
    }
    
    int main(){
    	string a;
    	cin>>a;
    	cout<<manacher(a)<<endl;
    	return 0;
    }
    
    //luogu3805
    

    day4 trie

    原理

    trie的原理实际上就是建一棵树,将枝干化为字母,点化为另一些代号。

    代码

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    using namespace std;
    const int MM=1000000+20;
    int n,m,map[MM][29],gg=1,tim[MM];
    bool judge[MM],wrong,repeat;
    char a[55];
    
    void insert(char *s){
    	int len=strlen(s);
    	int fa=1;
    	for(int i=0;i<len;i++){
    		int son=s[i]-'a';
    		if(!map[fa][son]) map[fa][son]=++gg;
    		fa=map[fa][son];
    	}
    	judge[fa]=true;
    }
    
    void search(char *s){
    	int len=strlen(s);
    	int fa=1;
    	for(int i=0;i<len;i++){
    		int son=s[i]-'a';
    		if(!map[fa][son]){
    			wrong=true;
    			return ;
    		}
    		fa=map[fa][son];
    	}
    	if(!judge[fa]){
    		wrong=true;
    		return ;
    	}
    	else
    	if(!tim[fa]) tim[fa]++;
    	else{
    		repeat=true;
    		return ;
    	}
    }
    
    int main(){
    	cin>>n;
    	while(n--){
    		cin>>a;
    		insert(a);
    	}
    	cin>>m;
    	while(m--){
    		cin>>a;
    		wrong=repeat=false;
    		search(a);
    		if(wrong){
    		cout<<"WRONG"<<endl;
    		}
    		else
    		if(repeat){
    		cout<<"REPEAT"<<endl;
    		}
    		else cout<<"OK"<<endl;
    	}
    }
    //luogu2580
    //主要看insert部分(建树)和search(查询)部分
    

    野鸡

    trie可以来将重边合并并且统计,看这道题:

    题目描述

    给定一颗n个节点的无根树,每条边上附有一个小写英文字母。

    于是一条路径对应一个字符串。

    一共有q次询问,每次询问以节点u为起点的非空字符串中有多少字典序严格小于字符串

    u—>v。

    输入格式

    第一行,两个个整数n,q。

    接下来n-1行,每行两个整数,一个小写字母u,v,c。 表示存在字母为c的树边(u,v)。保证u≠v。

    接下来q行,每行两个整数u,v。

    输出格式

    q行,每行一个答案。

    附上代码(每次写到后面我就变懒了):

    #include <bits/stdc++.h>
    using namespace std;
    int n, q;
    int link[4010][26], cnt[4010], pos[4010], now;
    int tmp[4010], key[4010][4010], num;
    char s[2];
    int head[4010];
    struct node {
        int to, next, val;
    } l[8010];
    void dfs(int s, int from) {//from==fa 
        for (int i = head[s]; i != -1; i = l[i].next) {
            if (l[i].to == from)
                continue;//防止往回走 
            if (!link[pos[s]][l[i].val])
                link[pos[s]][l[i].val] = pos[l[i].to] = ++now;
            else
                pos[l[i].to] = link[pos[s]][l[i].val];//直到这里,仍是都只是普通的trie建树 
            cnt[pos[l[i].to]]++;//求的是去到
            dfs(l[i].to, s);
        }
    }//dfs本质上是通过trie建树来将重边打包合并 
    void solve(int s) {
        for (int i = 0; i < 26; i++)
            if (link[s][i])
                tmp[link[s][i]] = num, num += cnt[link[s][i]], solve(link[s][i]);
    }
    int main() {
        scanf("%d%d", &n, &q);//输入 
        memset(head, -1, sizeof head);//用前向星存的边 
        for (int i = 0, one, two; i < n - 1; i++) {
            scanf("%d%d%s", &one, &two, s);
            one--, two--;//说明将编号改为0~n-1; 
            l[2 * i].to = two, l[2 * i].next = head[one], l[2 * i].val = s[0] - 'a', head[one] = 2 * i;
            l[2 * i + 1].to = one, l[2 * i + 1].next = head[two], l[2 * i + 1].val = s[0] - 'a',
                      head[two] = 2 * i + 1;//普通的前向星 (双向) 
        }
        for (int i = 0; i < n; i++) {
            memset(link, 0, sizeof link);
            memset(cnt, 0, sizeof cnt);
            memset(pos, 0, sizeof pos);
            now = 0;
            dfs(i, -1);
            memset(tmp, 0, sizeof tmp);
            num = 0;
            solve(0);
            for (int j = 0; j < n; j++) key[i][j] = tmp[pos[j]];
        }
        int u, v;
        while (q--) {
            scanf("%d%d", &u, &v);
            u--, v--;
            printf("%d
    ", key[u][v]);
        }
        return 0;
    }
    
    

    day5 AC自动机

    说实在话,我现在都忘了它是什么了。

    大脑回顾中

    大脑回顾中

    大脑回顾中

    大脑回顾中

    大脑回顾中

    想到了:

    给定n个模式串和1个文本串,求有多少个模式串在文本串里出现过。

    这个,还是直接看代码吧,只要是之前trie的建树&fail数组的查询

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    #include<vector>
    #include<queue>
    using namespace std;
    const int MM=2*1e6+20;
    int n,pre[MM],road[MM][29],gg;
    char a[MM];
    int fail[MM];
    
    void trie(char *s){
    	int lena=strlen(a),fa=0;
    	for(int i=0;i<lena;i++){
    		int son=s[i]-'a'+1;
    		if(!road[fa][son]) road[fa][son]=++gg;
    		fa=road[fa][son];
    	}
    	pre[fa]++;
    }
    queue<int> q;
    
    void make_fail(){
    	for(int i=1;i<=26;i++) if(road[0][i]) fail[road[0][i]]=0,q.push(road[0][i]); 
    	while(!q.empty()){
    		int fa=q.front();
    		q.pop();
    		for(int i=1;i<=26;i++){
    				if(road[fa][i]) {fail[road[fa][i]]=road[fail[fa]][i];q.push(road[fa][i]);} 
    				else road[fa][i]=road[fail[fa]][i];	
    		}
    	}
    }
    
    int quary(char *s){
    	int now=0,ans=0;
    	int l=strlen(s);
    	for(int i=0;i<l;i++){
    		int son=s[i]-'a'+1;
    		now=road[now][son];
    		for(int j=now;j&&pre[j]!=-1;j=fail[j]){
    			ans+=pre[j];
    			pre[j]=-1;
    		}
    	}
    	return ans;
    }
    
    int main(){
    	cin>>n;
    	for(int i=1;i<=n;i++){
    		cin>>a;
    		trie(a);
    	}
    	make_fail();
    	cin>>a;
    	cout<<quary(a)<<endl;
    } 
    

    然后luogu有三个模板,最后一个可以看看,优化好像是那个(

    还是直接附代码吧,topo排序我刚刚也没转过神来。

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    #include<algorithm>
    #include<queue>
    using namespace std;
    const int MM=2*1e5+20;
    int n,trie[MM][30],gg;
    char a[2000000+20];
    int kind[MM],kind2[MM],in[MM],ans[MM],vis[MM],fail[MM];
    queue<int> q;
    void insert(char *a,int kid){
    	int lena=strlen(a),fa=0;
    	for(int i=0;i<lena;i++){
    		int son=a[i]-'a'+1;
    		if(!trie[fa][son]) trie[fa][son]=++gg;
    		fa=trie[fa][son];
    	}
    	if(!kind[fa]) kind[fa]=kid;
    	kind2[kid]=kind[fa];
    }
    
    void make_fail(){
    	for(int i=1;i<=26;i++) if(trie[0][i]) q.push(trie[0][i]);
    	while(!q.empty()){
    		int fa=q.front();q.pop();
    		for(int i=1;i<=26;i++){
    			if(trie[fa][i]){fail[trie[fa][i]]=trie[fail[fa]][i];q.push(trie[fa][i]);in[fail[trie[fa][i]]]++;}
    			else trie[fa][i]=trie[fail[fa]][i];//并未满足条件,∴不需要++in[] 
    		}
    	} 
    }
    
    void quary(char *s){
    	int lens=strlen(s),now=0;
    	for(int i=0;i<lens;i++){
    		now=trie[now][s[i]-'a'+1];
    		ans[now]++;
    	}
    }
    
    void topsort(){
    	for(int i=1;i<=gg;i++) if(!in[i]) q.push(i);
    	while(!q.empty()){
    		int fa=q.front();
    		q.pop();
    		vis[kind[fa]]=ans[fa];
    		int grandfa=fail[fa];
    		ans[grandfa]+=ans[fa];
    		in[grandfa]--;
    		if(!in[grandfa]) q.push(grandfa); 
    	}
    	for(int i=1;i<=n;i++){
    		cout<<vis[kind2[i]]<<endl;
    	}
    }
    
    int main(){
    	cin>>n;
    	for(int i=1;i<=n;i++){
    		cin>>a;
    		insert(a,i);
    	}
    	make_fail();
    	cin>>a;
    	quary(a); 
    	topsort();
    }
    

    day6 考试

    day7 我写下这篇博客

    柒星完成博客啦

    我没有写扩展KMP,BM,sunday的一系列算法和尺取法的一些运用,一部分是它们并不难,可以说是简单,二是在今后的算法竞赛中(至少csp中)我还不大可能用到它们,但也算是掌握了一些。之后再看吧

    made by starseven

  • 相关阅读:
    去深圳办理港澳通行证签注延期
    預約申領往來港澳通行證及簽注x
    表格选中效果展示
    Purchase购物车实例分析
    IOS开发基础知识--碎片17
    IOS开发基础知识--碎片16
    IOS开发基础知识--碎片15
    IOS开发基础知识--碎片14
    IOS关于LKDBHelper实体对象映射插件运用
    IOS开发基础知识--碎片13
  • 原文地址:https://www.cnblogs.com/starseven/p/12942265.html
Copyright © 2020-2023  润新知