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

原文地址:https://www.cnblogs.com/starseven/p/12942265.html