暑期整理板子。

常用数据结构

单调队列

​ 求出连续区间内的最大值或最小值 ,维护一个单调的队列。

`
/*
ans数组维护第k个区间最大最小值。a数组存储数据,q数组模拟队列,m是区间长度,h维护队头指针
*/
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
h=1;t=0;
for(int i=1;i<=n;i++){//以i为结尾 
    while(h<=t && a[q[t]]<=a[i])t--;//删去队尾的无用元素 ,修改这里改变最大最小
    q[++t]=i;//进队
    while(i-q[h]>=m)h++;//队头删去在所需区间外的元素 
    if(i>=m)ans[i-m+1]= a[q[h]];//这么写省了先入m个元素的问题,便捷
}
`

单调栈

​ 很方便地求出某个数的左边或者右边第一个比它大或者小的元素,而且总时间复杂度O(N)。

//求出右边第一个比他大的元素下标
//a存数据,q存栈中下标,t维护栈顶。ans维护每个点右边第一个比他大的元素的下标,整体为单调递减栈
int t = 0;
for(int i = n; i>=1; i -- )//待修改
{
	while(t && a[q[t - 1]] <= a[i]) t --;//待修改
	if(t) ans[i] = q[t - 1];
	q[t ++ ] = i;
}

跳跃表

字符串

KMP

定义:有一个文本串S,和一个模式串P,现在要查找P在S中的位置

方法:①建立next数组(即最大公共前缀后缀),使得模式串可以更方便的进行回溯,不用从头在开始匹配。

​ ② 通过while循环获取到第一个位置。

​ ③ 周期性循环节长度为 : len-next【len】

#include<bits/stdc++.h>
using namespace std ;
string s,p;
const int maxn=1e5;
int nxtpos0[maxn];//nxtpos0为未优化版nxtpos数组
void getnxt()//获取next数组
{
    nxtpos0[0]=-1;
    int len = p.size();
    int j=0,k=-1;
    while(j <= len - 1)
    {
        if(k==-1||p[j]==p[k])
        {
            j++;
            k++;
            nxtpos0[j]=k;
        }
        else
        {
            k = nxtpos0[k];
        }
    }
}
void kmp()//对模式串和文本串进行匹配
{
    int i=0,j=0;
    int slen=s.size();
    int plen=p.size();
    while(i < slen)
    {
        if(j == -1||p[j] == s[i])
        {
            i++;
            j++;
        }
        else
        {
            j=nxtpos0[j];
        }
        if(j==plen)
        {
            cout<<i-j+1<<"\n";//输出匹配的到的模式串位置。
            i=i-j+1;
            j=0;
        }
    }
}
int main()
{
    cin>>s>>p;
    getnxt();
    kmp();
    return 0;
}

exKMP

定义母串S,和字串T,extend[i]表示T与S[i,n-1]的最长公共前缀。

https://blog.youkuaiyun.com/yiqzq/article/details/98663132?utm_medium=distribute.pc_relevant_download.none-task-blog-baidujs-1.nonecase&depth_1-utm_source=distribute.pc_relevant_download.none-task-blog-baidujs-1.nonecase

Manacher算法

字典树

也可解决区间亦或最值问题

//指针型字典树
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
struct trie//trie型结构体,建立树
{
    int cnt;//用作标记查找
    trie *next[26];//连接树与节点的指针
    bool flag;///单词结尾节点标记
};
trie *root=new trie;//建立根节点

void insert(char ch[])
{
    trie *p=root,*newnode;
    for(int i=0; ch[i]!='\0'; i++)
    {
        if(p->next[ch[i]-'a']==0)//这里以所有的字符全部为小写字母为例
        {
            newnode=new trie;///创建新节点
            newnode->flag=false;///初始化新节点结尾标记
            for(int j=0; j!=26; j++)
            {
                newnode->next[j]=NULL;///初始化所有孩子
            }
            newnode->cnt=1;///新节点次数初始化
            p->next[ch[i]-'a']=newnode;
            p=newnode;
        }
        else
        {
            p=p->next[ch[i]-'a'];
            p->cnt++;///对走过的节点次数进行计数
        }
    }
    p->flag=true;///标记结尾
}

int find(char ch[])///查询
{
    trie *p=root;
    for(int i=0; ch[i]!='\0'; i++)
    {
        if(p->next[ch[i]-'a']!=NULL)
            p=p->next[ch[i]-'a'];
        else
            return 0;
    }
    return p->cnt;
}

int deal(trie* T)//递归将字典树清空
{
    int i;
    if(T==NULL)
        return 0;
    for(i=0;i<10;i++)
    {
        if(T->next[i]!=NULL)
            deal(T->next[i]);
    }
    free(T);
    return 0;
}

int main()
{
    char ch[20];
    for(int i=0; i!=26; i++)
    {
        root->next[i]=NULL;
    }
    root->cnt=0;
    while(gets(ch))
    {
        if(!strcmp(ch,""))
            break;
        insert(ch);
    }
    while(scanf("%s",ch)!=EOF)
    {
        printf("%d\n",find(ch));
    }
    deal(root);
    return 0;
}

//数组型字典树
//
#include <stdio.h>
#include <string.h>
const int maxn=10000;//提前估计好可能会开的节点的个数

int tot;            //节点编号,模拟申请新节点,静态申请
int trie[10000][26]; //假设每个节点的分支有26个
bool isw[10000];     //判断该节点是不是单词结尾

void insert(char *s,int rt)//投入的参数是一个字符串和节点数,建立字典树
{
    for(int i=0; s[i]; i++)
    {
        int x=s[i]-'a';//假设单词都是小写字母组成
        if(trie[rt][x]==0) //没有,申请新节点
        {
            trie[rt][x]=++tot;//每个字符的编号
        }
        rt=trie[rt][x];
    }
    isw[rt]=true;//整个字符串读完后,在isw数组中记录第rt层为单词结尾
}

bool find(char *s,int rt)
{
    for(int i=0; s[i]; i++)
    {
        int x=s[i]-'a';//假设单词都是小写字母组成
        if(trie[rt][x]==0)
        {
            return false;
        }
        rt=trie[rt][x];
    }
    return isw[rt];
}

char s[22];//单词读入

int main()
{
    tot=0;//一开始没有节点

    int rt=++tot;//申请一个根节点
    memset(trie[rt],0,sizeof(trie[rt]));//初始化根节点
    memset(isw,false,sizeof(isw));

    while(scanf("%s",s),s[0]!='#') //新建字典,以一个'#'结束
    {
        insert(s,rt);
    }
    while(scanf("%s",s),s[0]!='#') //查单词,以一个'#'结束
    {
        if(find(s,rt))//从字典中查找单词
            printf("%s 在字典中\n",s);
        else
            printf("%s 不在字典中\n",s);
    }
    return 0;
}


AC自动机

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Node
{
	int cnt;//是否为该单词的最后一个结点 
	Node *fail;//失败指针 
	Node *next[26];//Trie中每个结点的各个节点 
}*queue[500005];//队列,方便用BFS构造失败指针 
char s[1000005];//主字符串 
char keyword[55];//需要查找的单词 
Node *root;//头结点 
void Init(Node *root)//每个结点的初始化 
{
	root->cnt=0;
	root->fail=NULL;
	for(int i=0;i<26;i++)
		root->next[i]=NULL;
}
void Build_trie(char *keyword)//构建Trie树 
{
	Node *p,*q;
	int i,v;
	int len=strlen(keyword);
	for(i=0,p=root;i<len;i++)
	{
		v=keyword[i]-'a';
		if(p->next[v]==NULL)
		{
			q=(struct Node *)malloc(sizeof(Node));
			Init(q);
			p->next[v]=q;//结点链接 
		}
		p=p->next[v];//指针移动到下一个结点 
	}
	p->cnt++;//单词最后一个结点cnt++,代表一个单词 
}
void Build_AC_automation(Node *root)
{
	int head=0,tail=0;//队列头、尾指针 
	queue[head++]=root;//先将root入队 
	while(head!=tail)
	{
		Node *p=NULL;
		Node *temp=queue[tail++];//弹出队头结点 
		for(int i=0;i<26;i++)
		{
			if(temp->next[i]!=NULL)//找到实际存在的字符结点 
			{ //temp->next[i] 为该结点,temp为其父结点 
				if(temp==root)//若是第一层中的字符结点,则把该结点的失败指针指向root 
					temp->next[i]->fail=root;
				else
				{
					//依次回溯该节点的父节点的失败指针直到某节点的next[i]与该节点相同,
                	//则把该节点的失败指针指向该next[i]节点; 
                	//若回溯到 root 都没有找到,则该节点的失败指针指向 root
					p=temp->fail;//将该结点的父结点的失败指针给p 
					while(p!=NULL)
					{
						if(p->next[i]!=NULL)
						{
							temp->next[i]->fail=p->next[i];
							break;
						}
						p=p->fail;
					}
					//让该结点的失败指针也指向root 
					if(p==NULL)
						temp->next[i]->fail=root;
				}
				queue[head++]=temp->next[i];//每处理一个结点,都让该结点的所有孩子依次入队 
			}
		}
	}
}
int query(Node *root)
{ //i为主串指针,p为模式串指针 
	int i,v,count=0;
	Node *p=root;
	int len=strlen(s);
	for(i=0;i<len;i++)
	{
		v=s[i]-'a';
		//由失败指针回溯查找,判断s[i]是否存在于Trie树中 
		while(p->next[v]==NULL && p!=root)
			p=p->fail;
		p=p->next[v];//找到后p指针指向该结点 
		if(p==NULL)//若指针返回为空,则没有找到与之匹配的字符 
			p=root;
		Node *temp=p;//匹配该结点后,沿其失败指针回溯,判断其它结点是否匹配 
		while(temp!=root)//匹配结束控制 
		{
			if(temp->cnt>=0)//判断该结点是否被访问 
			{
				count+=temp->cnt;//由于cnt初始化为 0,所以只有cnt>0时才统计了单词的个数 
				temp->cnt=-1;//标记已访问过 
			}
			else//结点已访问,退出循环 
				break;
			temp=temp->fail;//回溯 失败指针 继续寻找下一个满足条件的结点 
		}
	}
	return count;
}
int main()
{
	int T,n;
	scanf("%d",&T);
	while(T--)
	{
		root=(struct Node *)malloc(sizeof(Node));
		Init(root);
		scanf("%d",&n);
		for(int i=0;i<n;i++)
		{
			scanf("\n%s",keyword);
			Build_trie(keyword);
		}
		Build_AC_automation(root);
		scanf("\n%s",s);
		printf("%d\n",query(root));
	}
	return 0;
}

字符串hash

字符串hash就是通过不同的进制,将字符串变成数字来做hash,通过前缀的方式,可以O(1)快速查询某个子串是否出现过,同时可以水过数据范围小的一些字符串问题。

//核心思想:将字符串看成P进制数,P的经验值是131或13331,取这两个值的冲突概率低
//小技巧:取模的数用2^64,这样直接用unsigned long long存储,溢出的结果就是取模的结果
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ULL;
const int N=1e6+5;
ULL h[N], power[N]; // h[k]存储字符串前k个字母的哈希值, power[k]存储 P^k mod 2^64
char str[N];
int len,P;
// 初始化
void calHash()
{
	P=131;
    power[0] = 1;
    for (int i = 1; i <= len; i ++ )
    {
        h[i] = h[i - 1] * P + (str[i]-'a'+1);
        power[i] = power[i - 1] * P;
    }
}
 
 
// 计算子串 str[l ~ r] 的哈希值
ULL get(int l, int r)
{
    return h[r] - h[l - 1] * power[r - l + 1];
}
//该题是判断一个字符串两个区间的字符串是否相同
int main()
{
	scanf("%s",str+1);
	len=strlen(str+1);
	calHash();
	int m;
	scanf("%d",&m);
	while(m--)
	{
	  int l1,r1,l2,r2;
	  scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
	  if(get(l1,r1)==get(l2,r2))
	  {
	  	printf("Yes\n");
	  }
	  else
	  {
	  	printf("No\n");
	  }
	}
	return 0;
}

图论

最小生成树

Prim算法(适用点少)

Kruskal算法(适用边少)

最优比率生成树

最小k度限制生成树

Tarjan算法(缩点,算环)

矩阵快速幂(可求点到点的经历k条可重边的方案)

struct Mat{
    long long mat[111][111];
    Mat(){
        memset(mat,0,sizeof(mat));
    }
};

Mat operator*(const Mat &m1,const Mat &m2){
    Mat m;
    for(int i=0; i<=tn; ++i){
        for(int j=0; j<=tn; ++j){
            for(int k=0; k<=tn; ++k){
                m.mat[i][j]+=m1.mat[i][k]*m2.mat[k][j];
                m.mat[i][j]%=100000;
            }
        }
    }
    return m;
}

		Mat e,x;
        for(int i=0; i<=tn; ++i) e.mat[i][i]=1;
        for(int i=0; i<=tn; ++i){//对矩阵进行标记。
            if(flag[i]) continue;
            for(int j=0; j<4; ++j){
                if(flag[ch[i][j]]) continue;
                ++x.mat[i][ch[i][j]];
            }
        }
        while(n){
            if(n&1) e=e*x;
            x=x*x;
            n>>=1;
        }

DFS

​ 发现数据范围过大时,考虑双向dfs,即可将2的n此方复杂度变成2的n/2次方

BFS

在做bfs时,应保证队列中的元素具有单调性,即应为一层一层向外扩展。

​ 如果出现单层bfs或dfs过不去的情况,考虑双向bfs,即从起点和终点向中间扩展,时间为指数级缩小,

搜索

迭代加深:

A*:

IDA*:

拓扑排序(判断图是否为DAG图)

​ 判断一个图是否是 有向无环图(DAG) 。 拓扑排序原理: 对 DAG 的顶点进行排序,使得对每一条有向边 (u, v),均有 u(在排序记录中)比 v 先出现。亦可理解为对某点 v 而言,只有当 v 的所有源点均出现了,v 才能出现。

​ BFS法进行论证:

​ 算法流程:
​ 1.首先计算出所有节点的入度表。设nums为节点的数量和

​ 2.使用队列进行广度优先搜索,将所有入度为0的点入队,然后一边出队,一边判断哪些点的入度变为0了,同时将他们入队,不断迭代。

​ 3.每次执行出队操作时,将nums–,若存在如1-0,0-1的自环,则nums永不会为0,此时证明有环存在。

数论

叉乘,判断从同一个点出发的两直线相对顺时针or逆时针位置

double ans=(mx1-mid_x)*(my2-mid_y)-(mx2-mid_x)*(my1-mid_y);
ans>0||ans<0||ans=0;

矩阵快速幂

约数个数和欧拉函数

1.约数个数,存在N=p1的l1次方乘p2的l2次方*…乘Pk的lk次方

​ 约数个数即为(l1+1)乘(l2+1)乘(l3+1)

2.1到N中,从i=1加到i=n f(i) = nlog(n);

3.int范围内,最多的因字数有1600个。

阶乘分解:例如将5!分解为2的3次方乘3乘5;

分解方式:

图片

for(long long i=2;i<=n;i++)
        if (!vis[i])
        {
            long long ans=0;
            cout<<i<<" ";
            for(long long k=1;power(i,k)<=n;k++)//统计p出现的次数
                ans=ans+n/power(i,k);
            cout<<ans<<endl;
        }

杂论

ST表(区间最大最小值)

预:nlogn,查询为O(1)

//查询范围0-n-1
int a[1010];//原始输入数组
int st[1010][20];//st表

void init(int n)
{
    for (int i = 0; i < n; i++)
        st[i][0] = a[i];
	for (int i = 1; (1 << i) <= n; i++){
   		for (int j = 0; j + (1 << i) - 1 < n; j++)
        	st[j][i] = min(st[j][i - 1],st[j + (1 << (i - 1))][i - 1]);
	}
}
int search(int l, int r)
{
    int k = (int)(log((double)(r - l + 1)) / log(2.0));
    return min(st[l][k],st[r - (1 << k) + 1][k]);
}

博弈论

巴什博弈

即为一堆物品两个人轮流去,一次取一个或至多m个,问后手胜利前提下每次至多取多少个。

简称为:给你一个k,求最小的L使得k%(1+L)=0;

L为可以使第二个玩家获胜每次取纽扣的最大数量。

简单为:寻找k的大于等于2的因子,若能找到,则可确定第二个玩家必胜下每次最多选取的数量。

k倍动态减法有序(红书481页):

例如,也是一堆物品,轮流取,后一个人至多取前一个人的k倍,问谁赢

威佐夫博弈

即为两堆物品两个人轮流取,一次在一堆取k个或者两堆各取k个,问后手胜利前提下每次至多取多少个。

面对非奇异局势,先拿者必胜,反之,后拿者必胜。

通过floor((b-a)*((1.0+sqrt(5.0)/2.0)==a判断奇异局势。

尼姆博奕

简要解法:所有数取亦或判断是否为0,再或者取余再取亦或,再或者奇数堆取亦或或偶数堆取亦或。

n堆物品,轮流取

判断方式:将所有数亦或起来,判断最后的数是否为0,来判断先后手胜。

若询问第一步可创造出多少种必败态:

则先算出最后亦或结果t,判断有多少个数t^a[i]<a[i];

每堆至多取m个,此时将所有堆对m取余,在进行亦或运算判断0的情况

动态规划

待补:BZOJ5424

取数问题

​ 例如:给定一个矩阵,从左上角向右下角取数,取两轮,第一轮被取过得数会变成0,问两轮后取得最大值。

​ 解法:建立二维数组f【k】【i1】【i2】,k表示当前一共走了多少步,i1为目前第一轮的位置,i2为第二轮的位置,所以k-i1和k-i2确定具体坐标,代码如下:

for(int k = 2; k <= 2*n; k++) {
    for(int i1 = 1; i1 <= k-1; i1++) {
        for(int i2 = 1; i2 <= k-1; i2++) {
           	    int j1 = k-i1, j2 = k-i2;
                int &x = f[k][i1][i2];
                int t = w[i1][j1];
                if(i1!=i2) t += w[i2][j2];
                //不重合则需要加两个权重.
                x = max(x, f[k-1][i1-1][i2-1]+t);
                x = max(x, f[k-1][i1-1][i2]+t);
                x = max(x, f[k-1][i1][i2-1]+t);
                x = max(x, f[k-1][i1][i2]+t);
                //保留最大属性
        }
    }
}

cout << f[n*2][n][n] << endl;//输出时注意后两个属性应相同,为行数。
return 0

01背包

STL用法

unordered_map(哈希表)

//unordered_map内存占用率较高
unordered_map<int, string> myMap={{ 5, "张大" },{ 6, "李五" }};//使用{}赋值
myMap[2] = "李四";  //使用[ ]进行单个插入,若已存在键值2,则赋值修改,若无则插入。
myMap.insert(pair<int, string>(3, "陈二"));//使用insert和pair插入,直接赋值也可以

优先队列

//升序队列,小顶堆
priority_queue <int,vector<int>,greater<int> > q;
//降序队列,大顶堆
priority_queue <int,vector<int>,less<int> >q;

//greater和less是std实现的两个仿函数(就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了)

运算符重载

struct node //运算符重载<
{
    int x;
    friend bool operator< (node n1, node n2)
    {
        return n1.x < n2.x;
    }
};

问题总结:

图论算边权时

1.图论算顶点到叶子节点的路径值的和时,如果有除以2的操作,一定注意是仅针对某一条边,还是整条路径。尤其是向下取整时,构造奇数边权的样例防止错误。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值