面向考研的数据结构板子

数据结构板子

考研复习对着书看了一遍数据结构之后总觉得缺了点什么,正好以前打比赛的板子也找不到了,就在这写一点数据结构的基础板子找找手感,举的例子也都是一些简单题,希望能对复习有所帮助。我在写板子的时候尽量附上了可供测试的数据,并尽量找到oj的链接以便您能调试评测,如果妹有附上说明我真的妹有找到请理解(或者说我是真的懒)。请注意,作者的水平有限,很有可能会出现他意想不到的bug,或者他只过了弱测,并没有进行更强数据的校验,如果您发现出现了错误或者bug,请及时通知我,给您带来的不便敬请谅解。

另:本人整理的顺序是按照自己的复习顺序来的,并且掺杂很多自己的理解,很有可能与书上的定义冲突,如果发现存在不一致的现象,请以教材为准,快速查找请使用ctrl+f。

1 栈

栈是一个很基础的数据结构,我更愿意把它当作具有一定操作约束的线性表。这个线性表只能再一段插入(入栈)、删除(出栈),并且表内的元素具有后入先出的特点。

1.1 栈的操作

题目描述

假设给定的整数栈初始状态为空,栈的最大容量为100。从标准输入中输入一组栈操作,按操作顺序输出出栈元素序列。栈操作:1表示入栈操作,后跟一个整数**(不为1、0和-1)为入栈元素**;0表示出栈操作;-1表示操作结束。

输入形式

从标准输入读取一组栈操作,入栈的整数和表示栈操作的整数之间都以一个空格分隔。

输出形式

在一行上按照操作的顺序输出出栈元素序列,以一个空格分隔各元素,最后一个元素后也要有一个空格。如果栈状态为空时进行出栈操作,或栈满时进行入栈操作,则输出字符串“error”,并且字符串后也要有一空格。所有操作都执行完后,栈也有可能不为空。

样例

【样例输入】

1 3 1 5 1 7 0 0 1 8 0 1 12 1 13 0 0 0 0 1 90 1 89 0 -1

【样例输出】

7 5 8 13 12 3 error 89  
题解

使用数组模拟栈,并特判何时error。

#include
struct stack{
	int top;
	int data[1020];
};
struct stack s;

int i, n, m;
int main()
{	
	s.top = 0;
	while(~scanf("%d", &n))
	{
		if(n == 1)
		{
			scanf("%d",&m);
			if(s.top>=100)
			{
				printf("error ");
			}
			else
			{
				s.data[s.top]=m;
				s.top++;
			}
		}
		else if(n == 0)
		{
			if(s.top<1)
			{
				printf("error ");
			}
			else
			{
				printf("%d ",s.data[s.top - 1]);
				s.top--;
			}
		}
		else if(n == -1)
		{
			break;
		}
	}
	return 0;
}

1.2 计算器(后缀表达式板子题)

题目描述

从标准输入中读入一个整数算术运算表达式,如24 / ( 1 + 5%3 + 36 / 6 / 2 - 2) * ( 12 / 2 / 2 )= ,计算表达式结果,并输出。

要求:

  1. 表达式运算符只有+、-、*、/、%,表达式末尾的=字符表示表达式输入结束,表达式中可能会出现空格;

  2. 表达式中会出现圆括号,括号可能嵌套,不会出现错误的表达式;

  3. 出现除号/时,以整数相除进行运算,结果仍为整数,例如:5/3结果应为1。

  4. 要求采用逆波兰表达式来实现表达式计算。

输入形式

从键盘输入一个以=结尾的整数算术运算表达式。操作符和操作数之间可以有空格分隔。

输出形式

在屏幕上输出计算结果(为整数,即在计算过程中除法为整除)。

样例

【输入】

24 / ( 1 + 5%3 + 36 / 6 / 2 - 2) * ( 12 / 2 / 2 ) =

【输出】

18
题解

本体维护两个栈,一个是运算符栈,另一个是数字栈,在将中缀表达式转换为后缀表达式的过程中完成运算。(难点在于运算符的优先级,所以有人偷懒直接把优先级表打出来了)

#include
#define MAXSIZE 100
typedef int ElemType;
struct stack{
	int Top;
	ElemType data[MAXSIZE];
};
typedef struct stack Stack;
typedef struct stack *Stackp;
Stack OPFIGURE,OPTHETA;
void  push( Stackp s,  ElemType item )
{
	s->data[++s->Top] = item; 
}
int pop( Stackp s)
{
	return s->data[s->Top--]; 
}
int Gettop( Stackp s )
{
	return s->data[s->Top];
}
char Pre(char theta1, char theta2)//运算符优先级比较
{
	int i, j;
	char pre[8][8] = {// +   -   *   /   (   )   =  %
		             {'>','>','<','<','<','>','>','<'},  
                     {'>','>','<','<','<','>','>','<'},  
                     {'>','>','>','>','<','>','>','>'},  
                     {'>','>','>','>','<','>','>','>'},  
                     {'<','<','<','<','<','=','0','<'},  
                     {'>','>','>','>','0','>','>','>'},  
                     {'<','<','<','<','<','0','=','<'},
					 {'>','>','>','>','<','>','>','>'},}; 
                
    switch(theta1){
    	case '+': i = 0; break;  
        case '-': i = 1; break;  
        case '*': i = 2; break;  
        case '/': i = 3; break;  
        case '(': i = 4; break;  
        case ')': i = 5; break;
        case '=': i = 6; break;
        case '%': i = 7; break;
	}
	
    switch(theta2){
    	case '+': j = 0; break;  
        case '-': j = 1; break;  
        case '*': j = 2; break;  
        case '/': j = 3; break;  
        case '(': j = 4; break;  
        case ')': j = 5; break;
        case '=': j = 6; break;
        case '%': j = 7; break;
    }
    return(pre[i][j]);
}
int Op(int a, char theta, int b)//运算符含义表达
{
	int result;
	
	switch(theta){
		case'+':return a + b;
		case'-':return a - b;
		case'*':return a * b;
		case'%':return a % b;
		case'/':                     //判断除数是否为0,若除数为零返回错误提示
		        if(b != 0)
		        return a / b;
		        else
		        {
		        	exit(0);
				}
	}
    
}
int IsTheta(char c) //判断是否为运算符,是运算符返回1,若不是返回0
{
	switch(c){
		case '+':   
        case '-':  
        case '*':   
        case '/':   
        case '(':   
        case ')': 
        case '%': 
        case '=': 
                 return 1;
		 default:
		         return 0;		   
	}
}
int EvaluateExpression(char *expression)  //表达式运算 
{
	ElemType a, b, theta, tmp, X1, X2;
	
	char ch;
	int i = 0;
	
	OPFIGURE.Top = -1;
	OPTHETA.Top = -1;
	push(&OPTHETA,'=');  //把 = 压入运算符栈 
	ch=expression[i++];     // ch 读取表达式的下一个字符  
	
	while(ch != '=' || Gettop(&OPTHETA) != '=')
	{
		if(IsTheta(ch)) //判断是否为运算符 
		{
			switch(Pre(Gettop(&OPTHETA), ch)) // 比较 ch 和栈顶运算符  OPTHETA.data 的优先级
			{
				case'<':
						push(&OPTHETA,ch);
						ch = expression[i++];
						break;
				case'>':
						theta = pop(&OPTHETA); 
					    b = pop(&OPFIGURE);
					    a = pop(&OPFIGURE);
					    push(&OPFIGURE,Op( a, theta, b));
					    break;
				case'=':
						tmp = pop(&OPTHETA);
					    ch = expression[i++]; //读取下一位字符并将指针向后偏移一位
					    break;
			} 
			
		}
		else if(isdigit(ch)) //判断是否为数字 
		{
			X1 = ch - '0'; //将字符形式转化为数字
			push(&OPFIGURE, X1);
			X2 = X1;
			ch = expression[i++]; //读取下一位字符并将指针向后偏移一位
			
			while(isdigit(ch)) //判断下一位是否还是数字
			{
				X1 = ch - '0';
				X2 = 10 * X2 + X1; //归并至X2
				tmp = pop(&OPFIGURE);
				push(&OPFIGURE,X2);
				ch = expression[i++];  //读取下一位字符并将指针向后偏移一位
				
			} 
		}
		else if(ch == ' ') //判断是否为空格
		{
			while(ch == ' ')
			{
				ch = expression[i++];
			}
		}
		else //出现非法字符 
		{
			exit(0); 
		}
	}
	return(Gettop(&OPFIGURE));
}
int main()
{	
	char expression[100];
	int result;
	gets(expression);
	result = EvaluateExpression(expression);
	printf("%d\n",result);
	return 0;
}

2 队列

队列也是一种具有操作约束的线性表,对于他的操作只能在一端插入,而在另一端删除。(先进先出)

[2.1 约瑟夫问题](P1996 约瑟夫问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))

题目描述

n个人围成一圈,从第一个人开始报数,数到 m的人出列,再由下一个人重新从 1开始报数,数到 m的人再出圈,依次类推,直到所有的人都出圈,请输出依次出圈人的编号。

输入格式

输入两个整数 n,m。

输出格式

输出一行 n 个整数,按顺序输出每个出圈人的编号。

样例
样例输入
10 3
样例输出
3 6 9 2 7 1 8 5 10 4
题解

这道题有多种办法解决,如使用循环链表去模拟过程,但是显然模拟的时间会很长很长;可以使用递归,递归公式f(N, M) = (f(N - 1, M) + M) % N;我在这里使用了打表,简单但是不大好理解。

#include
int f[105];
int main(){
	int n, m;
	scanf("%d%d",&n,&m);
	int i = n;
	int sb = 0;
	int s = 0;
	while(i != 0){
		sb++;
		if(sb > n)sb = sb - n;
		if(f[sb] == 0)s++;
		if(s == m){
			printf("%d ",sb);
			f[sb] = 1;
			i--;
			s = 0;
		}
	}
}

3 树

树是一种层次性的关系,这种层次性的关系带给我们的是增删改查操作的效率的提高,查找和遍历是我们的主旋律(树搜的题好多);

比如二分查找就是典型的树搜的思想,因为二分的查找过程构成了一棵树。

[3.1 二分查找](P2249 【深基13.例1】查找 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))

题目描述

输入 $n(n <10^6) 个不超过 10^9 的单调不减的(就是后面的数字不小于前面的数字)非负整数 {n},然后进行 m(m < 10^5) 次询问。对于每次询问,给出一个整数 q(q < 10^9),要求输出这个数字在序列中第一次出现的编号,如果没有找到的话输出 -1 。

输入格式

第一行 2 个整数 n 和 m,表示数字个数和询问次数。

第二行 n 个整数,表示这些待查询的数字。

第三行 m 个整数,表示询问这些数字的编号,从 1 开始编号。

输出格式

m 个整数表示答案。

样例
样例输入
11 3
1 3 3 3 5 7 9 11 13 15 15
1 3 6
样例输出
1 2 -1
题解

二分不难,但是最重要的是在二分的时候学会边界控制,边界控制是一件经常被忽略的事情,请务必牢记。(其实是对自己说的)这里放一个我觉得写的很好的二分总结

#include
const int MAXN = 1e6 + 10;
int a[MAXN];

int read(){//快读
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		x=x*10+c-'0';
		c=getchar();
	}
	return x*f;
}

int find(int x, int n){
	
	int l = 1;
	int r = n;
	while(l < r){
		int mid = l + (r - l) / 2;
		if(a[mid] >= x){
			r = mid;
		}
		else{
			l = mid + 1;
		}
		//printf("%d %d\n", l, r);
	}
	
	if(a[l] == x)return l;
	else return -1;
}

int main(){
	int n, m;
	n = read();
	m = read();
	for(int i = 1;i <= n;i++){
		a[i] = read();
	}
	for(int i = 1;i <= m;i++){
		int baga = read();
		int ans = find(baga, n);
		printf("%d ",ans);
	}
	return 0;
	
}

[3.2 求先序序列]([P1030 NOIP2001 普及组] 求先序排列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn))

题目描述

给出一棵二叉树的中序与后序排列。求出它的先序排列。(约定树结点用不同的大写字母表示,且二叉树的节点个数 <= 8)。

输入格式

共两行,均为大写字母组成的字符串,表示一棵二叉树的中序与后序排列。

输出格式

共一行一个字符串,表示一棵二叉树的先序。

样例
样例输入
BADC
BDCA
样例输出
ABCD
题解

递归递归递归递归递归递归!!!!!!!!!!!

#include
#include
#include
#include
using namespace std;

void before(string mid, string after){
	if(mid.size() > 0){
		char ch = after[after.size() - 1];
		printf("%c", ch);
		int find = -1;
		find = mid.find(ch);
		before(mid.substr(0, find), after.substr(0, find));
		before(mid.substr(find + 1), after.substr(find, mid.size() - find - 1));
	}
}

int main(){
	string x, y;
	cin>>x;
	cin>>y;
	before(x, y);
	return 0;
}

在树的使用过程中,还要注意查找和遍历的不同之处!

层序遍历就使用队列就可以不断的把结点的左右孩子入队,并按照队列的顺序一个节点一个节点的把其左右孩子入队即可;我们寻找叶子节点可以使用前序遍历,求二叉树高度可以使用后续遍历,也如上题所示,使用中序遍历和其他一种遍历即可确定二叉树。

3.3 二叉树问题

题目描述

如下图所示的一棵二叉树的深度、宽度及结点间距离分别为:

  • 深度:4
  • 宽度:4
  • 结点 8 和 6 之间的距离:8
  • 结点 7 和 6 之间的距离:3

其中宽度表示二叉树上同一层最多的结点个数。

面向考研的数据结构板子_第1张图片

给定一颗以 1 号结点为根的二叉树,请求出其深度、宽度和两个指定节点 x, y$之间的距离。

输入格式

第一行是一个整数,表示树的结点个数 n。
接下来 n - 1行,每行两个整数 u, v,表示树上存在一条连接 u , v u, v u,v 的边。
最后一行有两个整数 x, y,表示求 x, y之间的距离。

输出格式

输入三行,每行一个整数,依次表示二叉树的深度、宽度和 x, y之间的距离。

样例
样例输入
10                                
1 2                            
1 3                            
2 4
2 5
3 6
3 7
5 8
5 9
6 10
8 6
样例输出
4
4
8
题解

我才不会说这道题其实很难(用LCA树剖会更快),但是如果数据足够弱的话,我们用简单的方法也可以将其解决。

#include
using namespace std;
vector  vv[105];
int f[105], D, d[105], H, h[105], ans;

void build(int sb){
	for(int i = 0;i < vv[sb].size();i++){
		if(f[sb] != vv[sb][i]){
			f[vv[sb][i]] = sb;
			d[vv[sb][i]] = d[sb] + 1;
			D = max(D, d[vv[sb][i]]);
			h[d[vv[sb][i]]]++;
			build(vv[sb][i]);
		}
	}
}

int main(){
	int n, u, v;
	int back = 0;
	scanf("%d", &n);
	for(int i = 1;i < n;i++){
		scanf("%d%d", &u, &v);
		vv[u].push_back(v);
		vv[v].push_back(u);
	}
	scanf("%d%d", &u, &v);
	build(1);
	for(int i = 1;i <= n;i++){
		H = max(H, h[i]);
	}
	if(d[u] > d[v]){//如果u深则反转 
		swap(u, v);
		back = 1;
	}
	while(d[u] != d[v]){
		v = f[v];
		ans += 1 + back;
	}
	while(u != v){
		u = f[u];
		v = f[v];
		ans += 3;
	} 
	printf("%d\n%d\n%d\n", D + 1, H, ans);
	return 0;
	
}

3.4 平衡树

【模板】普通平衡树

题目描述

您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:

  1. 插入 x 数
  2. 删除 x 数(若有多个相同的数,因只删除一个)
  3. 查询 x 数的排名(排名定义为比当前数小的数的个数 +1 )
  4. 查询排名为 x 的数
  5. 求 x 的前驱(前驱定义为小于 x,且最大的数)
  6. 求 x 的后继(后继定义为大于 x,且最小的数)

输入格式

第一行为 n,表示操作的个数,下面 n n n 行每行有两个数 opt 和 x,opt 表示操作的序号1 ~ 6

输出格式

对于操作3,4,5,6每行输出一个数,表示对应答案

样例
样例输入
10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598
样例输出
106465
84185
492737
题解

平衡树问题,三种解法供君选择,treap更贴近于书上写的做法,而以我本人的懒肯定还是树状数组,但是也贴上了剩下两种办法的代码,方便学习。(剩下两种我连看都没看,完全不保证其正确性)

treap:

#include
using namespace std;
struct Treap
{
	int data;
	int value;
	int left;
	int right;
	int siz;
};
Treap t[100005];
int cnt;
int root;
void up(int x)
{
	t[x].siz=t[t[x].left].siz+t[t[x].right].siz+1;
}
void right_rorate(int &now)
{
	int tmp=t[now].left;
	t[now].left=t[tmp].right;
	t[tmp].right=now;
	t[tmp].siz=t[now].siz;
	up(now);
	now=tmp;
}
void left_rorate(int &now)
{
	int tmp=t[now].right;
	t[now].right=t[tmp].left;
	t[tmp].left=now;
	t[tmp].siz=t[now].siz;
	up(now);
	now=tmp;
}
void insert(int &now,int data)
{
	if(now==0)
	{
		now=++cnt;
		t[now].siz=1;
		t[now].data=data;
		t[now].value=rand()*rand()%19620817;
		return ;
	}
	t[now].siz++;
	if(data>=t[now].data)
	{
		insert(t[now].right,data);
	}
	else
	{
		insert(t[now].left,data);
	}
	if(t[now].left!=0&&t[now].value>t[t[now].left].value)
	{
		right_rorate(now);
	}
	if(t[now].right!=0&&t[now].value>t[t[now].right].value)
	{
		left_rorate(now);
	}
	up(now);
}
void erase(int &now,int data)
{
	t[now].siz--;
	if(t[now].data==data)
	{
		if(t[now].left==0&&t[now].right==0)
		{
			now=0;
			return ;
		}
		if(t[now].left==0||t[now].right==0)
		{
			now=t[now].left+t[now].right;
			return ;
		}
		if(t[t[now].left].value<t[t[now].right].value)
		{
			right_rorate(now);
			erase(t[now].right,data);
			return ;
		}
		else
		{
			left_rorate(now);
			erase(t[now].left,data);
			return ;
		}
	}
	if(t[now].data>=data)
	{
		erase(t[now].left,data);
	}
	else
	{
		erase(t[now].right,data);
	}
	up(now);
}
int rank(int now,int data)
{
	if(now==0)
	{
		return 0;
	}
	if(data>t[now].data)
	{
		return t[t[now].left].siz+1+rank(t[now].right,data);
	}
	return rank(t[now].left,data);
}
int find(int now,int rank)
{
	if(rank==t[t[now].left].siz+1)
	{
		return t[now].data;
	}
	if(rank>t[t[now].left].siz+1)
	{
		return find(t[now].right,rank-t[t[now].left].siz-1);
	}
	return find(t[now].left,rank);
}
int query_pre(int now,int data)
{
	if(now==0)
	{
		return 0;
	}
	if(t[now].data>=data)
	{
		return query_pre(t[now].left,data);
	}
	int tmp=query_pre(t[now].right,data);
	if(tmp==0)
	{
		return t[now].data;
	}
	return tmp;
}
int query_suf(int now,int data)
{
	if(now==0)
	{
		return 0;
	}
	if(t[now].data<=data)
	{
		return query_suf(t[now].right,data);
	}
	int tmp=query_suf(t[now].left,data);
	if(tmp==0)
	{
		return t[now].data;
	}
	return tmp;
}
int main()
{
	srand(19620817);
	int n;
	cin>>n;
	int opt,data;
	for(int i=1;i<=n;i++)
	{
		scanf("%d %d",&opt,&data);
		if(opt==1)
		{
			insert(root,data);
		}
		if(opt==2)
		{
			erase(root,data);
		}
		if(opt==3)
		{
			printf("%d\n",rank(root,data)+1);
		}
		if(opt==4)
		{
			printf("%d\n",find(root,data));
		}
		if(opt==5)
		{
			printf("%d\n",query_pre(root,data));
		}
		if(opt==6)
		{
			printf("%d\n",query_suf(root,data));
		}
	}
	return 0;
}

树状数组:

#include
using namespace std;

const int maxn=100050;
int q[maxn], a[maxn], p[maxn], c[maxn];
int cnt = 0;

inline int read()
{
	int x=0,t=1;char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-')t=-1;ch=getchar();}
	while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
	return x*t;
}

int hash(int x){
	return lower_bound(q + 1,q + 1 + cnt, x) - q;
}

int lowbit(int x){
	return x&-x;
}

void add(int x,int p)
{
	while(x <= cnt)
	{
		c[x] += p;
		x += lowbit(x);
	}
}
int sum(int x)
{
	int res = 0;
	while(x)
	{
		res += c[x];
		x -= lowbit(x);
	}
	return res;
}

int query(int x)
{
	int t = 0;
	for(int i = 19;i >= 0;i--)
	{
		t += 1 << i;
		if(t > cnt || c[t] >= x) t -= 1 << i;
		else x -= c[t];
	}
	return q[t + 1];
}

int main(){
	int n;
	n = read();
	for(int i = 1;i <= n;i++){
		p[i] = read();
		a[i] = read();
		if(p[i] != 4)q[++cnt] = a[i];
	}
	sort(q + 1, q + 1 + cnt);
	cnt = unique(q + 1, q + 1 + cnt) - (1 + q);
	for(int i = 1;i <= n;i++)
	{
		if(p[i] == 1) add(hash(a[i]), 1);
		if(p[i] == 2) add(hash(a[i]), -1);
		if(p[i] == 3) printf("%d\n",sum(hash(a[i]) - 1) + 1);
		if(p[i] == 4) printf("%d\n",query(a[i]));
		if(p[i] == 5) printf("%d\n",query(sum(hash(a[i]) - 1)));
		if(p[i] == 6) printf("%d\n",query(sum(hash(a[i])) + 1));
	}
	return 0;
} 

红黑树:

#include 
using namespace std;
#define R true
#define B false
const int maxn = 1e6 + 1;
const int npos = INT_MAX;
int ch[maxn][2], sz[maxn], cnt[maxn], p[maxn], keys[maxn];
bool color[maxn];
int root, tree_size;
const int nil = 0;
inline void rotate(int x, bool right)
{
    //right == 0 -> rotateL
    //right == 1 -> rotateR
    int y = ch[x][!right];
    ch[x][!right] = ch[y][right];
    if (ch[y][right] != nil)
        p[ch[y][right]] = x;
    p[y] = p[x];
    if (p[x] == nil)
        root = y;
    else
        ch[p[x]][x == ch[p[x]][1]] = y;
    ch[y][right] = x;
    p[x] = y;
    sz[y] = sz[x];
    sz[x] = sz[ch[x][0]] + sz[ch[x][1]] + cnt[x];
}
inline void transplant(int to, int from)
{
    //把from节点移接到to节点下成为其子树,用于删除操作
    p[from] = p[to];
    if (p[to] == nil)
        root = from;
    else
        ch[p[to]][to == ch[p[to]][1]] = from;
}
inline int findMin(int x)
{
    while (ch[x][0] != nil)
        x = ch[x][0];
    return x;
}
inline int findMax(int x)
{
    while (ch[x][1] != nil)
        x = ch[x][1];
    return x;
}
inline int findNode(int k)
{
    int x = root;
    while (x != nil)
    {
        if (keys[x] == k)
            return x;
        //k大则右,k小则左
        x = ch[x][keys[x] < k];
    }
    return npos;
}
inline int find(int k)
{
    int x;
    if ((x = findNode(k)) != npos)
        return keys[x];
    return npos;
}
inline void putFixUp(int z)
{
    //修复put破坏的红黑性质
    while (color[p[z]] == R)
    {
        int fa = p[z], grand = p[fa];
        bool is_left = fa == ch[grand][0];
        int y = ch[grand][is_left];
        if (color[y] == R)
        {
            color[y] = color[fa] = B;
            color[grand] = R;
            z = grand;
        }
        else
        {
            if (z == ch[fa][is_left])
            {
                z = fa;
                rotate(z, !is_left);
            }
            color[p[z]] = B;
            color[p[p[z]]] = R;
            rotate(grand, is_left);
        }
    }
    color[root] = B;
}
inline void put(int k)
{
    int x = root, y = nil;
    while (x != nil)
    {
        y = x;
        ++sz[y];
        if (keys[x] == k)
        {
            ++cnt[x];
            return;
        }
        x = ch[x][keys[x] < k];
    }
    int z = ++tree_size;
    keys[z] = k;
    color[z] = R;
    p[z] = y;
    sz[z] = cnt[z] = 1;
    if (y == nil)
        root = z;
    else
        ch[y][keys[y] < k] = z;
    putFixUp(z);
}
inline void delFixUp(int x)
{
    //修复del破坏的红黑性质
    while (x != root && color[x] == B)
    {
        int fa = p[x];
        bool is_left = x == ch[p[x]][0];
        int w = ch[fa][is_left];
        if (color[w] == R) //case 1,will enter case 234 later
        {
            color[fa] = R;
            color[w] = B;
            rotate(fa, !is_left);
            w = ch[fa][is_left];
        }
        if (color[ch[w][0]] == B && color[ch[w][1]] == B) //case 2,will not enter case 34
        {
            color[w] = R;
            x = p[x];
        }
        else
        {
            if (color[ch[w][is_left] == B]) //case 3,will enter case 4
            {
                color[w] = R;
                color[ch[w][!is_left]] = B;
                rotate(w, is_left);
                w = ch[fa][is_left];
            }
            //case 4,after which the "while" will finish
            color[w] = color[fa]; //swap the color of w and x->p(w->p)
            color[fa] = B;
            color[ch[w][is_left]] = B;
            rotate(p[w], !is_left);
            x = root;
        }
    }
    color[x] = B;
}
inline void del(int k)
{
    int z = root, w = nil;
    while (z != nil)
    {
        w = z;
        --sz[w];
        if (k == keys[z])
            break;
        z = ch[z][keys[z] < k];
    }
    if (z != nil)
    {
        if (cnt[z] > 1)
        {
            --cnt[z];
            return;
        }
        int y = z, x;
        bool oldColor = color[y];
        if (ch[z][0] == nil)
        {
            x = ch[z][1];
            transplant(z, ch[z][1]);
        }
        else if (ch[z][1] == nil)
        {
            x = ch[z][0];
            transplant(z, ch[z][0]);
        }
        else
        {
            y = findMin(ch[z][1]);
            oldColor = color[y];
            x = ch[y][1];
            if (p[y] == z)
                p[x] = y;
            else
            {
                //把y移接到z下,沿路减小sz,注意不能直接--sz[tempy]
                int tempy = y;
                while (tempy != z)
                {
                    sz[tempy] -= cnt[y];
                    tempy = p[tempy];
                }
                transplant(y, ch[y][1]);
                ch[y][1] = ch[z][1];
                p[ch[y][1]] = y;
            }
            transplant(z, y);
            ch[y][0] = ch[z][0];
            p[ch[y][0]] = y;
            color[y] = color[z];
            sz[y] = sz[ch[y][0]] + sz[ch[y][1]] + cnt[y];
        }
        if (oldColor == B)
            delFixUp(x);
    }
    else
    {
        //没找到,恢复之前减掉的sz
        while (w != nil)
        {
            ++sz[w];
            w = p[w];
        }
    }
}
inline void inorderWalk(int x, void pred(int k, int cnt))
{
    //中序遍历树,这里没有用到
    if (ch[x][0] != nil)
        inorderWalk(ch[x][0], pred);
    pred(keys[x], cnt[x]);
    if (ch[x][1] != nil)
        inorderWalk(ch[x][1], pred);
}
inline int countLower(int rend, bool canEqual)
{
    //canEqual为true时,会把rend排在与它相等的元素之后,这里没有用到
    int x = root;
    int ret = 0;
    while (x != nil)
    {
        if (keys[x] < rend || (canEqual && !(rend < keys[x]) && !(keys[x] < rend)))
        {
            ret += sz[ch[x][0]] + cnt[x];
            x = ch[x][1];
        }
        else
            x = ch[x][0];
    }
    return ret;
}
inline int countBetween(int lend, int rend)
{
    return countLower(rend, true) - countLower(lend, false);
}
inline int findNext(int k)
{
    put(k);
    int ret, x = findNode(k);
    if (ch[x][1] != nil)
        ret = keys[findMin(ch[x][1])];
    else
    {
        while (ch[p[x]][1] == x)
            x = p[x];
        if (p[x] == nil)
            ret = npos;
        else
            ret = keys[p[x]];
    }
    del(k);
    return ret;
}
inline int findPrev(int k)
{
    put(k);
    int ret, x = findNode(k);
    if (ch[x][0] != nil)
        ret = keys[findMax(ch[x][0])];
    else
    {
        while (ch[p[x]][0] == x)
            x = p[x];
        if (p[x] == nil)
            ret = npos;
        else
            ret = keys[p[x]];
    }
    del(k);
    return ret;
}
inline int findKthSmallest(int k)
{
    int x = root;
    while (x != nil)
    {
        int l = ch[x][0], r = ch[x][1];
        if (sz[l] + 1 <= k && sz[l] + cnt[x] >= k)
            return keys[x];
        else
        {
            if (sz[l] + cnt[x] < k)
            {
                k -= sz[l] + cnt[x];
                x = r;
            }
            else
                x = l;
        }
    }
    return npos;
}
int main()
{
    int n, opt, x;
    scanf("%d", &n);
    while (n--)
    {
        scanf("%d%d", &opt, &x);
        switch (opt)
        {
        case 1:
            put(x);
            break;
        case 2:
            del(x);
            break;
        case 3:
            printf("%d\n", countLower(x, false) + 1);
            break;
        case 4:
            printf("%d\n", findKthSmallest(x));
            break;
        case 5:
            printf("%d\n", findPrev(x));
            break;
        case 6:
            printf("%d\n", findNext(x));
            break;
        }
    }
    return 0;
}

4 堆

我们不但要考虑数据的先后顺序,还要考虑优先级别的时候,就想到了优先队列,但如果单纯用数组链表去实现优先队列的话总会在有些操作上耗费时间多,因此堆应运而生。堆使用完全二叉树存储,顶点最大叫做大根堆,顶点最小叫做小根堆。

4.1 小根堆

题目描述

堆是一种重要的数据结构,它可以在O(logn)O(log⁡n)的时间内,维护一些元素的最大值或最小值。堆可以分为大根堆和小根堆,在这道题中你的任务就是实现一个小根堆。

本题禁用C++

输入格式

第一行一个正整数nn,表示操作次数。

接下来nn行,每行表示一次操作,有如下三种格式:

  • 1 x:表示向堆中插入一个元素x
  • 2:表示删除堆顶元素
  • 3:输出堆顶元素(不删除!
输出格式

对于所有的3类操作,输出堆顶的元素,每个一行。

在所有操作结束后,将堆中的元素从小到大依次输出到一行中,用一个空格隔开。

输入样例
8
1 2
1 1
3
2
1 5
3
1 0
3
输出样例
1
2
0
0 2 5
数据范围

n≤200000

题解

建堆并查找

#include 
#include 
int len = 0;
int a[200001] = {-1};
int cmp(const void *a,const void *b){
	return *(int*)a - *(int*)b;
}

void swap(int* x,int* y){
    int t = *x;
    *x = *y;
    *y = t;
}
void put(int x){
	int fa, son;
	a[++len] = x;
	son = len;
	while(son > 1){
		int fa = son / 2;
		if(a[son] >= a[fa])return;
		else {
			swap(&a[son],&a[fa]);
			son = fa;
		}
	}
}
void del(){
	int fa, son;
	a[1] = a[len];
	fa = 1;
	while(fa * 2 <= len){
		son = fa * 2;
		if(son < len && a[son] > a[son + 1])son++;
        if(a[fa] < a[son])break;
        else{
            swap(&a[fa],&a[son]);
            fa = son;
        }
	}
	len--;
}

int get(){
	return a[1];
}

int main(){
	int n;
	scanf("%d",&n);
	int op;
	int x;
	while(scanf("%d",&op)!=EOF){
		if(op == 1){
			scanf("%d",&x);
			put(x);
		}
		else if(op == 2){
			del();
		}
		else if(op == 3){
			int an = get();
			printf("%d\n",an);
		}
	}
	qsort(a,len + 1,sizeof(a[0]),cmp);
	for(int k = 1;k <= len;k++){
		printf("%d ",a[k]);
	}
}

4.2 判断堆

题目描述

给出一个长度为n的数列,请你判断它是否可以作为一个小根堆或者大根堆的数列。如果可以是小根堆数列,输出Min heap;如果可以是大根堆数列,输出Max heap;如果不可能是小根堆也不可能是大根堆,输出Not a heap!

输入格式

第一行一个正整数T,表示数据组数。

接下来的每组数据都包含两行,第一行为正整数n,表示序列长度;接下来一行,有n个由空格隔开的正整数,表示给出的数列。

输出格式

对于每一组数据,输出Min heapMax heapNot a heap!,每个一行。

输入样例
3
6
1 2 6 5 7 13
3
5 2 1
9
3 1 4 1 5 9 2 6 5
输出样例
Min heap
Max heap
Not a heap!
题解
#include 
#include 
int a[100001];
int len;
bool judgemin(int now){
	int next = now * 2;
	//查看左儿子
	if(next <= len){//有左儿子
		//查看右儿子
		if(next + 1 <= len){ //有右儿子
			if(a[now] <= a[next] && a[now] <= a[next + 1]){//检查堆性质
				return judgemin(next) & judgemin(next + 1);//递归
			}
			else return false;//不是堆返回
		}
		else return a[now] <= a[next];//只有左儿子
	}
	else return true;//没有左儿子
}

bool judgelarge(int now){
	int next = now * 2;
	//查看左儿子
	if(next <= len){//有左儿子
		//查看右儿子
		if(next + 1 <= len){ //有右儿子
			if(a[now] >= a[next] && a[now] >= a[next + 1]){//检查堆性质
				return judgelarge(next) & judgelarge(next + 1);//递归
			}
			else return false;//不是堆返回
		}
		else return a[now] >= a[next];//只有左儿子
	}
	else return true;//没有左儿子
}

int main(){
	int T;
	scanf("%d",&T);
	for(int i = 0;i < T;i++){
		scanf("%d",&len);
		for(int j = 1;j <= len;j++){
			scanf("%d",&a[j]);
		}
		if(a[1] < a[2]){
			if(judgemin(1) == true)printf("Min heap\n");
			else printf("Not a heap!\n");
		}
		else if(a[1] > a[2]){
			if(judgelarge(1) == true)printf("Max heap\n");
			else printf("Not a heap!\n");
		}
		else {
			int sb = 2;
			while(sb <= len){
				if(a[sb] == a[sb * 2])sb = sb * 2;
				else if(a[sb] < a[sb * 2]){
					if(judgemin(1))printf("Min heap\n");
					else printf("Not a heap!\n");
					break;
				}
				else if(a[sb] > a[sb * 2]){
					if(judgelarge(1))printf("Max heap\n");
					else printf("Not a heap!\n");
					break;
				}
			}
		}
	}
}

4.3 最优二叉树(哈夫曼树)

构建的这棵树具有这样的性质,即在上述搜索频率下,搜索代价之和最小,记该最小值为 Ans,求 Ans。

输入

第一行为一个正整数,表示 n

第二行 n 个正整数,分别表示 f1,f2…fn,

第三行 n+1 个正整数,分别表示 p0,p1…pn,

输出

输出一行,Ans 的值

输入样例
3
1 2 3
1 2 3 4
输出样例
29
题解
#include 
int main(){
	int n;
	scanf("%d",&n);
	int f[n + 1];
	for(int i = 1;i <= n;i++)scanf("%d",&f[i]);
	int p[n + 1];
	for(int i = 0;i <= n;i++)scanf("%d",&p[i]);
	long long e[n + 2][n + 1];
	long long w[n + 2][n + 1];
	for(int i = 1;i <= n + 1;i++){
		e[i][i - 1] = 0;
		w[i][i - 1] = p[i - 1];
	}
	for(int l = 1;l <= n;l++){
		for(int i = 1;i <= n - l + 1;i++){
			int j = i + l - 1;
			e[i][j] = 9223372036854775807;
			w[i][j] = w[i][j - 1] + f[j] + p[j];
			for(int r = i;r <= j;r++){
				long long t = e[i][r - 1] + e[r + 1][j] + w[i][j];
				//printf("e[%d][%d]%lld\n",i,j,t);
				if(t < e[i][j]){
					e[i][j] = t;
				}
			}
		}
	}
	printf("%lld",e[1][n]);
}

4.4 并查集

题目描述

如题,现在有一个并查集,你需要完成合并和查询操作。

输入格式

第一行包含两个整数 N,M ,表示共有 N 个元素和 M 个操作。

接下来 M 行,每行包含三个整数 Z_i,X_i,Y_i 。

当 Z_i=1 时,将 X_i 与 Y_i 所在的集合合并。

当 Z_i=2 时,输出 X_i 与 Y_i 是否在同一集合内,是的输出
Y ;否则输出 N

输出格式

对于每一个 Z_i=2 的操作,都有一行输出,每行包含一个大写字母,为 Y 或者 N

样例
样例输入
4 7
2 1 2
1 1 2
2 1 2
1 3 4
2 1 4
1 2 3
2 1 4
样例输出
N
Y
N
Y
题解

并查集大部分都是放在比较综合的题里去使用,这里只给出模板,何时使用还请仔细思考。

#include 
int f[10004];
void init(int n){
	for(int i = 1;i <= n;i++){
		f[i] = i;
	}
}

int find(int x){
	if(f[x] == x)return x;
	else return f[x] = find(f[x]);
}

int main(){
	int n , m;
	scanf("%d%d",&n,&m);
	init(n);
	for(int i = 0;i < m;i++){
		int op;
		int x, y;
		scanf("%d%d%d", &op, &x, &y);
		if(op == 1){
			f[find(x)] = find(y);
		}
		else {
			if(find(x) == find(y))printf("Y\n");
			else printf("N\n");
		}
	}
}

5 图

我们使用图来表示多对多的关系,图中包含一组顶点和一组顶点对(一般情况下不考虑重边和回路),我们喜欢使用邻接矩阵和邻接表去存储图,事实上有更多的办法去存储,比如说链式前向星,我在下面的题目中很多使用了这种数据结构,相信聪明的你一定可以学会。(如果可以使用矩阵,谁又不想用呢/(ㄒoㄒ)/~~)

5.1 BFS

题目描述

这天你来到一个神秘的宫殿,宫殿的地面是由黑色与白色两种颜色的瓷砖贴成的。

你可以选择其中的一块瓷砖作为起点,但每次只能向相邻(上下左右四个方向)且颜色相同的瓷砖移动。

请问你在选择一个起点后,最多能够到达多少块瓷砖?(注意,瓷砖是可以重复走的)

输入格式

第一行两个整数 n,m(1≤n,m≤100)n,m(1≤n,m≤100) 。

接下来 n 行每行 m 个字符代表宫殿的瓷砖颜色分布(b 代表黑色瓷砖,w 代表白色瓷砖)。

输出格式

输出一行一个整数,表示最多能够到达的瓷砖数。

样例输入
4 5
wbwbw
bbwbb
bbbbb
wwwww
样例输出
11
题解

本题按理说使用BFS或者DFS都可以,这里只放出BFS的代码了。(请更关注BFS或DFS的实现,而非本题如何解决,这个道理同样适用于上面的绝大多数例题,其实我们在还原先序序列的地方使用的就是DFS并递归的思想,我才不会说是我太懒了不想做题 (´つヮ⊂︎))

#include 
#include
#include
using namespace std;

const int maxn = 100;
char matrix[maxn + 1][maxn + 1];

int tep = 0;
int count = 1;

struct Node {
    int x,y;
} node;

int X[] = {0,0,1,-1};
int Y[] = {1,-1,0,0};

bool inque[maxn][maxn] = {false};
bool judge(int i,int j,int x,int y,int m,int n) {
    if(i < 0 || j < 0 || i >= n || j >= m || matrix[i][j] != matrix[x][y] || inque[i][j] == true)
        return false;
    return true;
}

void BFS(int i, int j,int n,int m) {
    queue<Node> que;                     //定义队列
    Node tep_node;

    tep_node.x = i,tep_node.y = j;
    que.push(tep_node);                      //入队
    inque[i][j] = true;                  //标记已经入队
    while(!que.empty()) {                //队列非空
        Node top = que.front();          //取出队首元素
        que.pop();                       //队首元素出队
        for(int i = 0; i < 4; ++i) {     //访问相邻的四个元素
        	int tepi = top.x;
        	int tepj = top.y;
            int nowI = top.x + X[i];
            int nowJ = top.y + Y[i];
            if(judge(nowI,nowJ,tepi,tepj,m,n)) {
                node.x = nowI,node.y = nowJ;
                que.push(node);          //入队
                inque[nowI][nowJ] = true;//标记已经入队
                count++;
            }
        }
    }
    if(count > tep)tep = count;
    count = 1;
}

int main() {
    int n, m;
	scanf("%d%d",&n,&m);

	for(int i = 0;i < n;i++){
		scanf("%s",&matrix[i]);
	}

    for(int i = 0; i < n; i++) {
        for(int j = 0; j < m;j++) {
            if(inque[i][j] == false) { //元素为1,并且元素未入过队
                BFS(i,j,n,m);
            }
        }
    }
    printf("%d",tep);
}

5.2 最短路径专题

5.2.1 Dijkstra

一定要注意在正权图中使用!

题解

Dijkstra:

#include

const int MaxN = 100010, MaxM = 500010;

struct edge
{
    int to, dis, next;
};

edge e[MaxM];
int head[MaxN], dis[MaxN], cnt;
bool vis[MaxN];
int n, m, s;

inline void add_edge( int u, int v, int d )
{
    cnt++;
    e[cnt].dis = d;
    e[cnt].to = v;
    e[cnt].next = head[u];
    head[u] = cnt;
}

struct node
{
    int dis;
    int pos;
    bool operator <( const node &x )const
    {
        return x.dis < dis;
    }
};

std::priority_queue<node> q;


inline void dijkstra()
{
    dis[s] = 0;
    q.push( ( node ){0, s} );
    while( !q.empty() )
    {
        node tmp = q.top();
        q.pop();
        int x = tmp.pos, d = tmp.dis;
        if( vis[x] )
            continue;
        vis[x] = 1;
        for( int i = head[x]; i; i = e[i].next )
        {
            int y = e[i].to;
            if( dis[y] > dis[x] + e[i].dis )
            {
                dis[y] = dis[x] + e[i].dis;
                if( !vis[y] )
                {
                    q.push( ( node ){dis[y], y} );
                }
            }
        }
    }
}


int main()
{
    scanf( "%d%d%d", &n, &m, &s );
    for(int i = 1; i <= n; ++i)dis[i] = 0x7fffffff;
    for( register int i = 0; i < m; ++i )
    {
        register int u, v, d;
        scanf( "%d%d%d", &u, &v, &d );
        add_edge( u, v, d );
    }
    dijkstra();
    for( int i = 1; i <= n; i++ )
        printf( "%d ", dis[i] );
    return 0;
}

spfa:(想了想还是把他放出来,负权图中可以使用)

#include
const long long inf=2147483647;
const int maxn=10005;
const int maxm=500005;
using namespace std;
int n,m,s,num_edge=0;
int dis[maxn],vis[maxn],head[maxm];
struct Edge
{
  int next,to,dis;
}edge[maxm]; //结构体表示静态邻接表
void addedge(int from,int to,int dis) //邻接表建图
{ //以下是数据结构书上的标准代码,不懂翻书看解释
  edge[++num_edge].next=head[from]; //链式存储下一条出边
  edge[num_edge].to=to; //当前节点编号
  edge[num_edge].dis=dis; //本条边的距离
  head[from]=num_edge; //记录下一次的出边情况
}
void spfa()
{
  queue<int> q; //spfa用队列,这里用了STL的标准队列
  for(int i=1; i<=n; i++) 
  {
    dis[i]=inf; //带权图初始化
    vis[i]=0; //记录点i是否在队列中,同dijkstra算法中的visited数组
  }
  q.push(s); dis[s]=0; vis[s]=1; //第一个顶点入队,进行标记
  while(!q.empty())
  {
    int u=q.front(); //取出队首
    q.pop(); vis[u]=0; //出队标记
    for(int i=head[u]; i; i=edge[i].next) //邻接表遍历,不多解释了(也可用vector代替)
    {
      int v=edge[i].to; 
      if(dis[v]>dis[u]+edge[i].dis) //如果有最短路就更改
      {
        dis[v]=dis[u]+edge[i].dis;
        if(vis[v]==0) //未入队则入队
        {
          vis[v]=1; //标记入队
          q.push(v);
        }
      }
    }
  }
}
int main()
{
  cin>>n>>m>>s;
  for(int i=1; i<=m; i++)
  {
    int f,g,w;
    cin>>f>>g>>w; 
    addedge(f,g,w); //建图,有向图连一次边就可以了
  }
  spfa(); //开始跑spfa
  for(int i=1; i<=n; i++)
    if(s==i) cout<<0<<" "; //如果是回到自己,直接输出0
      else cout<<dis[i]<<" "; //否则打印最短距离
  return 0;
} 

5.2.2 Floyd-Warshall

题目描述

给定 n 个点,m 条有向边,p个询问。

每个询问查询u到v的最短路径长度,若不存在路径则输出−1。

P.S. 点的编号从1到n

输入

第一行三个正整数n m p

接下来m行,每行三个正整数u v w,表示u到v具有长度为w的有向边

接下来p行,每行两个正整数u v,表示一个询问,u和v可能相同

输出

输出pp行,每行表示一个询问的答案

输入样例
3 3 2
1 2 3
2 3 1
3 1 2
1 2
1 3
输出样例
3
4
题解
#include
const long long INF = 0x3f3f3f3f3f3f3f3f;
const int MAXN = 505;
int main(){
    long long e[MAXN][MAXN];
    int  n, m, p, t1, t2, t3;
    scanf("%d%d%d",&n,&m,&p);
    for(int i = 1;i <= n;i ++){
        for(int j = 1;j <= n;j ++){
            if(i == j)e[i][j] = 0;
            else e[i][j] = INF;
        }
    }
    for(int i = 1;i <= m;i++){
        scanf("%d%d%d",&t1,&t2,&t3);
        if(e[t1][t2] > t3)e[t1][t2] = t3;
    }

    for(int k = 1;k <= n;k ++){
        for(int i = 1;i <= n;i ++){
            for(int j = 1 ;j <= n;j ++){
                if(e[i][j] > e[i][k] + e[k][j])e[i][j] = e[i][k] + e[k][j];
            }
        }
    }

    for(int i = 1;i <= p;i ++){
        int sb1, sb2;
        scanf("%d%d",&sb1,&sb2);
        if(e[sb1][sb2] != INF)printf("%lld\n", e[sb1][sb2]);
        else printf("-1\n");
    }
}

5.3 最小生成树

输入

输入一个带权图:

第一行两个正整数 n,m 表示图中的点数和边数。

接下来的 m 行,每行三个正整数 u,v,w表示一条连接 u,v且权值为 ww的边

输出

输出一行一个正整数,表示最小生成树的边权。

输入样例
3 3
1 2 1
1 3 2
2 3 1
输出样例
2
题解

两种方法一种Krusal一种prim,相信聪明的你很快就能发现哪一种是我抄的辣!

Krusal:

#include 
#include 
#include 
#define Inf 0x7fffffff
using namespace std;
struct node
{
    int u,v;
    int w;
    node(int a,int b,int x):u(a),v(b),w(x){}
};
vector<node> edge;
int vn,an;
int pa[100005];
bool cmp(const node &a,const node &b)
{
    return a.w<b.w;
}
int find(int x)
{
    return x==pa[x]?x:pa[x]=find(pa[x]);
}
long long kruskal()
{
    long long sumw=0,cnt=0;
    for(int i=1;i<=vn;i++) pa[i]=i;
    int Max = -1;
    for(int i=0;i<an;i++)
    {
        int x=find(edge[i].u),y=find(edge[i].v);
        if(x!=y)
        {
            pa[x]=y;
            sumw+=edge[i].w;
            Max = max(edge[i].w, Max);
            ++cnt;
        }
        if(cnt==vn-1) break;
    }
    return sumw;
}
int main()
{

        cin>>vn>>an;
        edge.clear();
        for(int i=1;i<=an;i++)
        {
            int a,b,x;
            cin>>a>>b>>x;
            edge.push_back(node(a,b,x));
        }
        sort(edge.begin(),edge.end(),cmp);
        printf("%lld",kruskal());

    return 0;
}

prim:

#include
using namespace std;
#define re register
#define il inline
il int read()
{
    re int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
    while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*f;
}//快读,不理解的同学用cin代替即可
#define inf 123456789
#define maxn 5005
#define maxm 200005
struct edge
{
	int v,w,next;
}e[maxm<<1];
//注意是无向图,开两倍数组
int head[maxn],dis[maxn],cnt,n,m,tot,now=1,ans;
//已经加入最小生成树的的点到没有加入的点的最短距离,比如说1和2号节点已经加入了最小生成树,那么dis[3]就等于min(1->3,2->3)
bool vis[maxn];
//链式前向星加边
il void add(int u,int v,int w)
{
	e[++cnt].v=v;
	e[cnt].w=w;
	e[cnt].next=head[u];
	head[u]=cnt;
}
//读入数据
il void init()
{
    n=read(),m=read();
    for(re int i=1,u,v,w;i<=m;++i)
    {
        u=read(),v=read(),w=read();
        add(u,v,w),add(v,u,w);
    }
}
il int prim()
{
	//先把dis数组附为极大值
	for(re int i=2;i<=n;++i)
	{
		dis[i]=inf;
	}
    //这里要注意重边,所以要用到min
	for(re int i=head[1];i;i=e[i].next)
	{
		dis[e[i].v]=min(dis[e[i].v],e[i].w);
	}
    while(++tot<n)//最小生成树边数等于点数-1
    {
        re int minn=inf;//把minn置为极大值
        vis[now]=1;//标记点已经走过
        //枚举每一个没有使用的点
        //找出最小值作为新边
        //注意这里不是枚举now点的所有连边,而是1~n
        for(re int i=1;i<=n;++i)
        {
            if(!vis[i]&&minn>dis[i])
            {
                minn=dis[i];
				now=i;
            }
        }
        ans+=minn;
        //枚举now的所有连边,更新dis数组
        for(re int i=head[now];i;i=e[i].next)
        {
        	re int v=e[i].v;
        	if(dis[v]>e[i].w&&!vis[v])
        	{
        		dis[v]=e[i].w;
        	}
		}
    }
    return ans;
}
int main()
{
    init();
    printf("%d",prim());
    return 0;
}

5.4 拓扑排序

我自己做了一道魔改的拓扑排序就不放上来了,放一道板子把。找了半天没找到拓扑排序的板子题,不找了,找了一道简单一点的最大拓扑排序。

题目描述

给定nn个点,mm条有向边,求拓扑排序。

P.S. 对于拓扑排序不唯一的情况,先输出序号大的点,再输出序号小的点。

输入

第一行两个正整数n m

接下来m行,每行两个正整数u v,表示u到v存在有向边

输出

输出一行,表示拓扑排序,点的序号以空格分隔

P.S. 点的序号从1到n,数据保证有解

输入样例
3 2
1 2
1 3
输出样例
1 3 2
题解

我偷懒使用了stl优先队列,想挑战自己的可以只用c写。

#include
using namespace std;
const int maxn = 4e5 + 5;
int head[maxn];
int ind[maxn];// 记录入度
int ans[maxn];//记录答案
int cnt;
int n, m;

struct EdgeNode{
	int to;
	int w;
	int next;
};
EdgeNode edge[maxn];

void init(){
	cnt = 0;
	memset(ans, 0, sizeof(ans));
	memset(head, -1, sizeof(head));
	memset(ind, 0, sizeof(ind));
}

void top(){
	priority_queue<int, vector<int>, less<int> > Q;
	for(int i = 1; i <= n; i++){
		if(!ind[i]){
			Q.push(i);
		}
	}
	int num;
	while(!Q.empty()){
		num = Q.top();
		ans[cnt++] = num;
		Q.pop();
		for(int i = head[num]; i != -1; i = edge[i].next){
			int v = edge[i].to;
			ind[v]--;
			if(ind[v] == 0)
				Q.push(v);
		}
	}

    for(int i = 0; i < cnt; i++)
    {
        if(i)
            printf(" ");
        printf("%d",ans[i]);
    }
    printf("\n");
}

int main(){
	cin >> n >> m;
	init();
	for(int i = 1 ; i <= m; i ++){
		int a,b;
		cin >> a >> b;
		edge[i].to = b;
		edge[i].next = head[a];
		head[a] = i;
		ind[b] ++;
	}
	top();

}

5.5 关键路径

输入

第一行是四个由空格隔开的整数 n (节点个数), m (边数), s (源点), t (终点)。此后的 m 行,每行三个正整数 a, b, c, 表示一条从节点 a 到节点 b 的一条长度为 c 的边。(节点个数小于 15 个)

输出

第一行输出关键通路的长度;

第二行到第 n+1 行输出每一个顶点的 TE, TL 和缓冲时间;

输出所有的关键通路。

输入样例
9 15 1 9
1 2 3
1 3 2
1 4 4
2 5 4
2 3 0
3 5 4
3 6 4
3 4 2
4 7 5
5 9 6
5 7 3
5 6 0
6 8 3
8 9 1
7 8 1
输出样例
Dis=13
Node 1: TE=   0  TL=   0  TL-TE= 0
Node 2: TE=   3  TL=   3  TL-TE= 0
Node 3: TE=   3  TL=   3  TL-TE= 0
Node 4: TE=   5  TL=   6  TL-TE= 1
Node 5: TE=   7  TL=   7  TL-TE= 0
Node 6: TE=   7  TL=   9  TL-TE= 2
Node 7: TE=  10  TL=  11  TL-TE= 1
Node 8: TE=  11  TL=  12  TL-TE= 1
Node 9: TE=  13  TL=  13  TL-TE= 0
[[1, 2, 5, 9], [1, 2, 3, 5, 9]]
题解

找不到样题,找了很久之前写的一个python。

[n,m,s,t]=[int(x) for x in input().split(" ")]
g=[[1e9 for i in range(n+1)] for j in range(n+1)]

for i in range(m):
    [a,b,c]=[int(x) for x in input().split(" ")]
    g[a][b]=c

indegree=[0 for i in range(n+1)]
topo=[]
for x in range(1,n+1):
    for y in range(1, n + 1):
        if g[x][y] < 1e9:
            indegree[y] += 1
zz=s
for i in range(1,n+1):
        x=zz
        zz=indegree[zz]
        topo.append(x)
        for y in range(1,n+1):
              if g[x][y]<1e9:
                    indegree[y]-=1
                    if indegree[y]==0:
                          indegree[y]=zz
                          zz=y
te=[0 for i in range(n+1)]
for i in topo:
    for j in range(1,n+1):
        if g[i][j]<1e9:
            if te[i]+g[i][j]>te[j]:
                te[j]=te[i]+g[i][j]
tl=[te[n] for i in range(n+1)]
for k in range(n-2,-1,-1):
    i=topo[k]
    for j in range(1,n+1):
        if g[i][j]<1e9:
            if tl[j]-g[i][j]<tl[i]:
                tl[i]=tl[j]-g[i][j]
pair=[[] for i in range(n+1)]

for i in range(1,n+1):
    for j in range(1,n+1):
        if g[i][j]<1e9:
            if te[i]==tl[j]-g[i][j]:
                pair[i].append(j)
arr=[]
tmp=[]
def genearr(x):
    global pair
    tmp.append(x)
    if x==t:
        arr.append(tmp.copy())
        tmp.remove(x)
        return
    for y in pair[x]:
        genearr(y)
    tmp.remove(x)
genearr(s)
array=[]
while len(arr)>0:
    lm = 1e9
    ln = 0
    for x,w in enumerate(arr):
        if len(w)<lm:
            lm=len(w)
            ln=x
    array.append(arr[ln])
    arr.remove(arr[ln])
print("Dis=", te[n], sep="")
for i in range(1,n+1):
    print("Node",i, end="")
    print(": TE= %3d" % (te[i]), sep="", end=" ")
    print(" TL= %3d" % (tl[i]), sep="", end=" ")
    print(" TL-TE= ", tl[i] - te[i], sep="")
print(array)

5.6 最大流

这段我只懂了个概念,剩下的屁都不会,如果您想学习我也不废话误人子弟,请点击[这里](网络最大流算法模板(EK+Dinic+Isap+Hlpp) - 知乎 (zhihu.com)),我觉得是一个很好的教程了。

6 排序和查找

很多简单方法来源百度百科,侵删。

排序和查找是大多数题不变的目的,因此理解排序和查找是非常必要的。

如果序列基本有序,则插入排序简单高效。

任何n个不同元素组成的序列平均具有n(n - 1) / 4个逆序对,因此交换相邻两个元素来排序的算法复杂度最好的情况下是O(n^2),所以为了提高效率,我们应该每次交换小区不止一个逆序对。

6.1 冒泡排序

#include 
 
#define ARR_LEN 255 /*数组长度上限*/
#define elemType int /*元素类型*/
 
/* 冒泡排序 */
/* 1. 从当前元素起,向后依次比较每一对相邻元素,若逆序则交换 */
/* 2. 对所有元素均重复以上步骤,直至最后一个元素 */
/* elemType arr[]: 排序目标数组; int len: 元素个数 */
void bubbleSort (elemType arr[], int len) {
    elemType temp;
    int i, j;
    for (i=0; i<len-1; i++) /* 外循环为排序趟数,len个数进行len-1趟 */
        for (j=0; j<len-1-i; j++) { /* 内循环为每趟比较的次数,第i趟比较len-i次 */
            if (arr[j] > arr[j+1]) { /* 相邻元素比较,若逆序则交换(升序为左大于右,降序反之) */
                temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
}
 
int main (void) {
    elemType arr[ARR_LEN] = {3,5,1,-7,4,9,-6,8,10,4};
    int len = 10;
    int i;
     
    bubbleSort (arr, len);
    for (i=0; i<len; i++)
        printf ("%d\t", arr[i]);
    putchar ('\n');
     
    return 0;
}

6.2 插入排序

public class Insertion
{
    public static void sort(Comparable[] a)
    {
        //将a[]按升序排列
        int N=a.length;
        for (int i=1;i<N;i++)
        {
        //将a[i]插入到a[i-1],a[i-2],a[i-3]……之中
            for(int j=i;j>0&&(a[j].compareTo(a[j-1])<0);j--)
            {
                Comparable temp=a[j];
                a[j]=a[j-1];
                a[j-1]=temp;
            }
        }
    }
}

6.3 堆排序

#include
#define F(i,n,m) for(int i=n;i<=m;i++)
using namespace std;
int main()
{
	priority_queue<int,vector<int>,greater<int> > q;//小根堆
	int n,x;
	cin>>n;
	F(i,1,n)
	{
		cin>>x;
		q.push(x);
	}
	F(i,1,n)
	{
		cout<<q.top()<<' ';
		q.pop();
	}
	return 0;
}

6.4 归并排序

#include
#include
using namespace std;
int n,a[500010],c[500010];
long long ans;

void msort(int b,int e)//归并排序
{
    if(b==e)  
		return;
    int mid=(b+e)/2,i=b,j=mid+1,k=b;
    msort(b,mid),msort(mid+1,e);
    while(i<=mid&&j<=e)
    	if(a[i]<=a[j])
    		c[k++]=a[i++];
    	else
    		c[k++]=a[j++],ans+=mid-i+1;//统计答案
    while(i<=mid)
    	c[k++]=a[i++];
    while(j<=e)
    	c[k++]=a[j++];
    for(int l=b;l<=e;l++)
    	a[l]=c[l];
} 

int main()
{
    scanf("%d",&n); 
    for(int i=1;i<=n;i++)
    	scanf("%d",&a[i]);
    msort(1,n);
    printf("%lld",ans);
    return 0;
}

6.5 二分排序

#include
#defind F(i,n,m) for(int i=n;i<=m;i++)
using namespace std;
int n,a[1000001];
void mysort(int l,int r)//自己写的快排 
{
    int mid=a[(l+r)/2];//找中间的数进行2分 
    int i=l,j=r;
    do{
        while(a[i]<mid)
		i++;//找左半部分大于中间数的 
        while(a[j]>mid)
		j--;//找右半部分小于中间数的 
        if(i<=j)
        {
            swap(a[i],a[j]);//换位 
            i++;//左指针右移 
            j--;//右指针左移 
        }
    }
	while(i<=j);
    if(l<j) mysort(l,j);
    if(i<r) mysort(i,r);
}
int main()
{
    cin>>n;
    F(i,1,n)
    cin>>a[i];
    random_shuffle(a+1,a+n+1); //打乱它 
    mysort(1,n);//快排 
    F(i,1,n)
    cout<<a[i]<<' ';//输出 
    return 0;
}

6.6 快排

#include
using namespace std;
int n,a[1000001];
void qsort(int l,int r)//应用二分思想
{
    int mid=a[(l+r)/2];//中间数
    int i=l,j=r;
    do{
        while(a[i]<mid) i++;//查找左半部分比中间数大的数
        while(a[j]>mid) j--;//查找右半部分比中间数小的数
        if(i<=j)//如果有一组不满足排序条件(左小右大)的数
        {
            swap(a[i],a[j]);//交换
            i++;
            j--;
        }
    }while(i<=j);//这里注意要有=
    if(l<j) qsort(l,j);//递归搜索左半部分
    if(i<r) qsort(i,r);//递归搜索右半部分
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    qsort(1,n);
    for(int i=1;i<=n;i++) cout<<a[i]<<" ";
}

java版来源于一个胖胖的人:

class Solution {
    private int length;
    public static void quicksort(int[] nums,int first, int last){
        if(first >= last){
            return;
        }
        int left_pointer = first - 1;
        int right_pointer = last;
        Random random = new Random();
        int num = random.nextInt(last - first + 1) + first; 
        int pivort = nums[num];
        nums[num] = nums[last];
        nums[last] = pivort;
        int pointer = first;
        while(pointer < right_pointer){
            if(nums[pointer] < pivort){
                left_pointer++;
                int temp = nums[left_pointer];
                nums[left_pointer] = nums[pointer];
                nums[pointer] = temp;
                pointer++;
            }else if(nums[pointer] > pivort){
                right_pointer--;
                int temp = nums[right_pointer];
                nums[right_pointer] = nums[pointer];
                nums[pointer] = temp;
            }else{
                pointer++;
            }
        }
        int temp = nums[right_pointer];
        nums[right_pointer] = nums[last];
        nums[last] = temp;
        quicksort(nums,first,left_pointer);
        quicksort(nums,right_pointer,last);
    }
    public int[] sortArray(int[] nums) {
        this.length = nums.length;
        quicksort(nums,0,length-1);
        return nums;
    }
}

6.7 桶排序

#include
#define F(i,n,m) for(int i=n;i<=m;i++)
using namespace std;
int main()
{
	map<int,int> a;
	int n;
	cin>>n;
	F(i,1,n)
	{
		int x;
		cin>>x;
		a[x]++;
	}
	map<int,int>::iterator it=a.begin();
	for(it;it!=a.end();it++)
	{
		aa:;
		if(it->second==0) continue;
		cout<<it->first<<' '; it->second--; goto aa;
	}
	return 0;
}

6.8 基数排序

#include 
using namespace std;

long sz[10][100000];
void js(long arr[], int N)
{
	int n = 1, a = 1;
	long max = arr[0];
	for (int i = 0; i<N; i++)//找到最大值
	{
		if (arr[i]>max)max = arr[i];
	}
	int y = 1;

	for (int i = 1; max / i>9; i *= 10)//读取最大值的位数
	{
		n++;
	}
	for (int i = 0; i<n; i++)//进行n此排序,即从个位排到第n位
	{
		int x = 0;
		for (int j = 0; j<N; j++)//将每个数按照顺序排到创建的数组里
		{
			sz[((int)(arr[j] / a) % 10)][j] = arr[j];
		}
        
		for (int k = 0; k<10; k++)//按顺序读取排好的数组
		{
			for (int l = 0; l<N; l++)
			{
				if (sz[k][l])
				{
					arr[x] = sz[k][l];
					x++;
				}
			}
		}
		for (int o = 0; o < 10; o++)//将创建的数组清零
			for (int p = 0; p < N; p++)
				sz[o][p] = 0;
		a *= 10;//a*10表示下一次循环排更高一位
	}
}


int main() {
	int N;
	cin>>N;
	long arr[100000];
	for(int i=0;i<N;i++)
	{
	    cin>>arr[i];
	}
	js(arr,N);
	for(int i=0;i<N;i++)
	{
	    cout<<arr[i]<<" ";
	}
}

说了这么多排序,放点查找;

6.9 线段树

#include
using namespace std;
int n,m,a[100010];

struct llk
{
	long long l,r,lazy,sum;
}tree[500010];

void build(int id,int l,int r)
{
	tree[id].l=l;
	tree[id].r=r;
	if(l==r)
	{
		tree[id].sum=a[l];
		return;
	}
	int mid=(l+r)/2;
	build(id*2,l,mid);
	build(id*2+1,mid+1,r);
	tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;
}

void down(int id)
{
	tree[id*2].lazy+=tree[id].lazy;
	tree[id*2].sum+=(tree[id*2].r-tree[id*2].l+1)*tree[id].lazy;
	tree[id*2+1].lazy+=tree[id].lazy;
	tree[id*2+1].sum+=(tree[id*2+1].r-tree[id*2+1].l+1)*tree[id].lazy;
	tree[id].lazy=0;
}

void update(int id,int l,int r,int v)
{
	if(tree[id].l>r || tree[id].r<l) return;
	if(tree[id].l>=l && tree[id].r<=r)
	{
		tree[id].lazy+=v;
		tree[id].sum+=(tree[id].r-tree[id].l+1)*v;
		return;
	}
	if(tree[id].lazy>0) down(id);
	update(id*2,l,r,v);
	update(id*2+1,l,r,v);
	tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;
}

long long query(int id,int l,int r)
{
	if(tree[id].l>r || tree[id].r<l) return 0;
	if(tree[id].l>=l && tree[id].r<=r) return tree[id].sum;
	if(tree[id].lazy>0) down(id);
	return query(id*2,l,r)+query(id*2+1,l,r);
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	build(1,1,n);
	for(int i=1;i<=m;i++)
	{
		int o,x,y;
		scanf("%d",&o);
		if(o==1)
		{
			int k;
			scanf("%d%d%d",&x,&y,&k);
			update(1,x,y,k);
		}
		else 
		{
			scanf("%d%d",&x,&y);
			printf("%lld\n",query(1,x,y));
		}
	}
}

6.10 主席树

#include
const int N=1000005;
using namespace std;
int a[N],n,q,rt[N*20];
inline int read(){
    int f=1,x=0;char ch;
    do{ch=getchar();if(ch=='-')f=-1;}while(ch<'0'||ch>'9');
    do{x=x*10+ch-'0';ch=getchar();}while(ch>='0'&&ch<='9');
    return f*x;
}
struct Persistable_Segment_Tree{
    int lc[N*20],rc[N*20],val[N*20],cnt;
    inline void build(int &o,int l,int r){
        o=++cnt;
        if(l==r){val[o]=a[l];return;}
        int mid=(l+r)>>1;
        build(lc[o],l,mid);build(rc[o],mid+1,r);
    }
    inline void ins(int &o,int pre,int l,int r,int q,int v){
        o=++cnt;lc[o]=lc[pre];rc[o]=rc[pre];val[o]=val[pre];
        if(l==r){val[o]=v;return;}
        int mid=(l+r)>>1;
        if(q<=mid)ins(lc[o],lc[pre],l,mid,q,v);
        else ins(rc[o],rc[pre],mid+1,r,q,v);
    }
    inline int query(int o,int l,int r,int q){
        if(l==r)return val[o];
        int mid=(l+r)>>1;
        if(q<=mid)return query(lc[o],l,mid,q);
        else return query(rc[o],mid+1,r,q);
    }
}T;
int main(){
    n=read();int m=read();
    for(int i=1;i<=n;i++)a[i]=read();
    T.build(rt[0],1,n);
    for(int i=1;i<=m;i++){
        int pre=read(),opt=read(),x=read();
        if(opt==1){int v=read();T.ins(rt[i],rt[pre],1,n,x,v);}
        if(opt==2){printf("%d\n",T.query(rt[pre],1,n,x));rt[i]=rt[pre];}
    }
}

6.11 可持久化并查集

#include 
#include 
#include 
using namespace std;
typedef long long LL;
#define dd c=getchar()
inline LL read(){LL a=0,b=1;char dd;while(!isdigit(c)&&c!='-')dd;
	if(c=='-'){b=-b;dd;}while(isdigit(c)){a=a*10+c-'0';dd;}return a*b;}
#undef dd
template <int n>
class vervec{
LL tree[20*n],ls[20*n],rs[20*n],hd[n],hd1[2*n],tt,t,nh;LL*a;
LL&ins(LL nod,LL l,LL r,LL x){
	if(l==r){return tree[nod];}
	int mid=l+r>>1;
	if(x<=mid){tree[++tt]=tree[ls[nod]];ls[tt]=ls[ls[nod]];rs[tt]=rs[ls[nod]];
		ls[nod]=tt;return ins(tt,l,mid,x);}
	if(x>mid){tree[++tt]=tree[rs[nod]];ls[tt]=ls[rs[nod]];rs[tt]=rs[rs[nod]];
		rs[nod]=tt;return ins(tt,mid+1,r,x);}
}
void chit(){LL x=hd[nh];hd[++t]=++tt;tree[tt]=tree[x];
	ls[tt]=ls[x];rs[tt]=rs[x];nh=t;}
int build(LL l,LL r){
	LL nod=++tt;if(l==r){tree[nod]=a[l];ls[nod]=rs[nod]=0;return nod;}
	LL mid=l+r>>1;
	ls[nod]=build(l,mid);
	rs[nod]=build(mid+1,r);
	return nod;
}
void init(){tt=0;hd[nh=0]=1;build(1,n);}
LL query(LL nod,LL l,LL r,LL x){
	if(l==r)return tree[nod];
	LL mid=l+r>>1;
	if(x<=mid)return query(ls[nod],l,mid,x);
	else if(x>mid)return query(rs[nod],mid+1,r,x);
}
public:
	vervec(){tt=0;t=0;nh=1;}
	void init(LL*x){a=x;init();}
	void mark(LL x){hd1[x]=nh;}
	LL&operator[](LL x){chit();return ins(hd[nh],1,n,x);}
	LL val(LL x){return query(hd[nh],1,n,x);}
	void rever(LL x){nh=hd1[x];}
};
vervec<120003>f,c;
LL getf(LL v){
	LL fa=f.val(v);
	if(fa==v)return v;
	return getf(fa);
}
void add(LL a,LL b){
	a=getf(a);
	b=getf(b);
	if(a==b)return;
	LL va=c.val(a),vb=c.val(b);
	if(va==vb){
		f[a]=b;
		c[b]++;
	}else if(va<vb){
		f[a]=b;
	}else{
		f[b]=a;
	}
}
LL sol[120003],n,m,x,y,z;
void init(LL n){
	for(LL i=1;i<=n;i++)sol[i]=i;
	f.init(sol);
	for(LL i=1;i<=n;i++)sol[i]=1;
	c.init(sol);
}
int main(){
	n=read();
	m=read();
	init(n);
	f.mark(0);
	c.mark(0);
	for(LL i=1;i<=m;i++){
		x=read();
		if(x==1){
			x=read();
			y=read();
			add(x,y);
		}else if(x==2){
			y=read();
			f.rever(y);
			c.rever(y);
		}else{
			x=read();
			y=read();
			puts(getf(x)==getf(y)?"1":"0");
		}
		f.mark(i);
		c.mark(i);
	}
	return 0;
}

7 散列

7.1 KMP

与其复杂的去阐述kmp的过程,不如从有限状态自动机的角度去理解他的执行过程,只要你画出了关于模式串的简化有限状态机的示意图,那么next数组可谓是手到擒来。另:把这个自动机推广为树形就会产生字典树(前缀树),就可以解决多维字符串匹配问题,通常把这种问题称为AC自动机。

#include
#include
char text[1000010];
char pattern[1000010];
int next[1000010];
void getNext(char s[],int len){
	int j = -1;
	next[0] = -1;
	for(int i = 1; i < len ;i++){
		while(j != -1 && s[i] != s[j + 1]){
			j = next[j];
		}
		if(s[i] == s[j + 1]){
			j++;
		}
		next[i] = j;
	}
}
int KMP(char text[],char pattern[]){
	int n = strlen(text), m = strlen(pattern);

	getNext(pattern,m);
	int ans = 0, j = -1;
	for(int i = 0;i < n;i++){
		while(j != -1 && text[i] != pattern[j + 1]){
			j = next[j];
		}
		if(text[i] == pattern[j + 1]){
			j++;
		}
		if(j == m - 1){
			ans++;
			printf("%d ",i - m + 1);
			j = next[j];
		}
	}
	return ans;
}

int main(){
	scanf("%s",&text);
	scanf("%s",&pattern);
	KMP(text,pattern);
	
}

7.2 AC自动机

没啥写的了放个AC自动机把,见HDU2222。

#include
#define maxn 1000001
using namespace std;
char s[151][maxn],T[maxn];
int n,cnt,vis[maxn],ans;
struct kkk{
	int son[26],fail,flag;
	void clear(){memset(son,0,sizeof(son));fail=flag=0;}
}trie[maxn];
queue<int>q;
void insert(char* s,int num){
	int u=1,len=strlen(s);
	for(int i=0;i<len;i++){
		int v=s[i]-'a';
		if(!trie[u].son[v])trie[u].son[v]=++cnt;
		u=trie[u].son[v];
	}
	trie[u].flag=num;			//变化1:标记为第num个出现的字符串
}
void getFail(){
	for(int i=0;i<26;i++)trie[0].son[i]=1;
	q.push(1);trie[1].fail=0;
	while(!q.empty()){
		int u=q.front();q.pop();
		int Fail=trie[u].fail;
		for(int i=0;i<26;i++){
			int v=trie[u].son[i];
			if(!v){trie[u].son[i]=trie[Fail].son[i];continue;}
			trie[v].fail=trie[Fail].son[i];
			q.push(v);
		}
	}
}
void query(char* s){
	int u=1,len=strlen(s);
	for(int i=0;i<len;i++){
		int v=s[i]-'a';
		int k=trie[u].son[v];
		while(k>1){
			if(trie[k].flag)vis[trie[k].flag]++;	//如果有模式串标记,更新出现次数
			k=trie[k].fail;
		}
		u=trie[u].son[v];
	}
}
void clear(){
	for(int i=0;i<=cnt;i++)trie[i].clear();
	for(int i=1;i<=n;i++)vis[i]=0;
	cnt=1;ans=0;
}
int main(){
	while(1){
		scanf("%d",&n);if(!n)break;
		clear();
		for(int i=1;i<=n;i++){
			scanf("%s",s[i]);
			insert(s[i],i);
		}
		scanf("%s",T);
		getFail();
		query(T);
		for(int i=1;i<=n;i++)ans=max(vis[i],ans);	//最后统计答案
		printf("%d\n",ans);
		for(int i=1;i<=n;i++)
		if(vis[i]==ans)
		printf("%s\n",s[i]);
	}
}

后记

这大概就是DS能出现的顶级难度了把,很多题估计我也写了好久也借鉴了别人的实现,如果遇到看不懂的可以简单看看功能函数的实现,而并非纠结细节上的代码,个人感觉算法题思路永远比代码重要,毕竟代码可以通过刷题练熟,但是思路没有的话跟海上的船没有前进的方向不是一样吗。

后续可能还会更更常用的东西或者一些东西的实现,但是感觉大概率是要鸽了。

  1. 肥猫土豆很多代码参考了别人的实现,如果碰巧参考了您的代码感到不适,请联系我删除;
  2. 肥猫土豆有些代码也只懂一点点理论,想要完全手撸出来也要很久,对于一个考研的小火汁来说无异于把他杀了,因此如果完全不懂,问了肥猫土豆也没有回应的话,大概率是他也不是很懂,或者他在忙着玩游戏没空回复您;
  3. 这篇文章是肥猫土豆耗时一天多的时间写完的,如果出现疏漏请及时提醒他,并且包容他的失误,可以偷偷嘲笑他的错误,但请至少不要谩骂或者诋毁;
  4. 这篇文章写出来就是给别人看的,毕竟肥猫土豆的起步也是看了好多好多人的代码,多亏了那些厉害的人,才有了今天的肥猫土豆,如有需要可以随意转发分享;
  5. 对于里面的很多题目,是完全超越了考研的要求,如若不适可以忽略,这篇文章的主要目的是对您的复习之路产生帮助,而非浪费您宝贵的备考时间去纠结完全不需要掌握的知识;
  6. 希望我的努力能给您带来帮助,祝您武运昌隆。

以后再说以后的事把,我要去卷了,总有人比你聪明还比你努力,看到这里还不快去学习!

你可能感兴趣的:(数据结构,数据结构,算法,c语言)