数据结构模板

RMQ(ST表查询区间最值)

//以查询最大值为例
状态表示: 集合:f(i,j)表示从位置i开始长度为2^j的区间的最大值;
           属性:MAX
状态转移: f(i,j)=max(f(i,j-1),f(i+(1<<(j-1)),j-1));
           含义:把区间[i,i+2^j],分成两半,[i,i+2^(j-1)]和[i+(1<<(j-1)),2^j],整个区间最大值就是这两段区间最大值的最大值
const int N=2e5+7,M=20;
int dp[N][M]; //存储区间最大值
int a[N];//存放每个点的值
//dp求从位置i开始长度为2^j的区间的最大值
for(int j=0;j<M;j++)
{
    for(int i=1;i+(1<<j)-1<=n;i++)
    {
        if(!j) dp[i][j]=a[i];
        else dp[i][j]=max(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
    }
}
//求任意区间的最大值;(可以预处理log)
int res=log(b-a+1)/log(2);
cout <<max(dp[a][res],dp[b-(1<<res)+1][res])<<endl;

滑动窗口

#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
int read() {
    int x=0; int f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') f=-1; ch=getchar();}
    while(ch>='0'&&ch<='9') {x=x*10+ch-'0'; ch=getchar();}
    return x*f;
}
const int maxn=5e6+100;
int num[maxn];
int q[maxn];
int Max[maxn];
int Min[maxn];
int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&num[i]);
	}
	 
	int s=1,e=0;//起点和终点//记录下标 
	for(int i=1;i<=n;i++){
		while(s<=e&&num[i]>=num[q[e]]) e--;//永无出头之日 
		q[++e]=i;
		while(s<=e&&q[e]-q[s]+1>m) s++; 
		Max[i]=num[q[s]]; 
	}//最大值
	//维护一个单调递减的对列
 
	s=1,e=0;
	for(int i=1;i<=n;i++){
		while(s<=e&&num[i]<=num[q[e]]) e--;
		q[++e]=i;
		while(s<=e&&q[e]-q[s]+1>m) s++;
		Min[i]=num[q[s]];
	}//维护一个递增的队列
	 
	for(int i=m;i<=n;i++){
		printf("%d ",Min[i]);
	}
	cout<<endl;
	for(int i=m;i<=n;i++){
		printf("%d ",Max[i]);
	}
}

并查集

朴素并查集:

int p[N]; //存储每个点的祖宗节点

// 返回x的祖宗节点
int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ ) p[i] = i;

// 合并a和b所在的两个集合:
p[find(a)] = find(b);

维护集合中的边的个数和点的个数

int pre[maxn];
int b[maxn];//边的个数
int d[maxn];//点的个数
for(int i=1;i<=n;i++){
	b[i]=0;
	d[i]=1;
	pre[i]=i;
}
int find(int x) {
    if (pre[x] == x) return x;
    return pre[x] = find(pre[x]);
}

void marge(int a,int b){
	int t1=find(a);
	int t2=find(b);
	b[t2]++;
	if(t1!=t2)
	{
		pre[t1]=t2;
		b[t2]+=b[t1];
		d[t2]+=d[t1];
	}
}

维护到祖宗节点距离的并查集

int p[N], d[N];
    //p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离
 
    // 返回x的祖宗节点
    int find(int x)
    {
        if (p[x] != x)
        {
            int u = find(p[x]);
            d[x] += d[p[x]];
            p[x] = u;
        }
        return p[x];
    }
 
    // 初始化,假定节点编号是1~n
    for (int i = 1; i <= n; i ++ )
    {
        p[i] = i;
        d[i] = 0;
    }
 
    // 合并a和b所在的两个集合:
    p[find(a)] = find(b);
    d[find(a)] = distance; // 根据具体问题,初始化find(a)的偏移量

单调栈

单调队列是一种思想, 每次取队头,队头如果满足条件,那么一定是最优的(最小或最大的,最长最远的,数量最多的,潜力更大,拓展性更强,生存能力更高,节点入队时间短的,等等....);然后把队尾那些"劣质”的节点信息给弹出;(一般队列里都存放下标)

滑动窗口

int num[maxn];
int q[maxn];
int Max[maxn];
int Min[maxn];
int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&num[i]);
	}
	 
	int s=1,e=0;//起点和终点//记录下标 
	for(int i=1;i<=n;i++){
		while(s<=e&&num[i]>=num[q[e]]) e--;//永无出头之日 
		q[++e]=i;
		while(s<=e&&q[e]-q[s]+1>m) s++; 
		Max[i]=num[q[s]]; 
	}//最大值
	//维护一个单调递减的对列
 
	s=1,e=0;
	for(int i=1;i<=n;i++){
		while(s<=e&&num[i]<=num[q[e]]) e--;
		q[++e]=i;
		while(s<=e&&q[e]-q[s]+1>m) s++;
		Min[i]=num[q[s]];
	}//维护一个递增的队列
	 
	for(int i=m;i<=n;i++){
		printf("%d ",Min[i]);
	}
	cout<<endl;
	for(int i=m;i<=n;i++){
		printf("%d ",Max[i]);
	}
}

最大子段和(限制区间长度)

这个就是滑动窗口的应用,首先维护前缀和,然后对于s[i],我们需要维护规定区间内的s[j]的最小值。最大字段和就是\(s[i]-s[j]_{min}\)

    for (int i = 1; i <= n; i ++ ) scanf("%d", &s[i]), s[i] += s[i - 1];
 
    int res = -INF;
    int hh = 0, tt = 0;
 
    for (int i = 1; i <= n; i ++ )//核心代码
    {
        if (q[hh] < i - m) hh ++ ;
        res = max(res, s[i] - s[q[hh]]);
        while (hh <= tt && s[q[tt]] >= s[i]) tt -- ;
        q[ ++ tt] = i;
    }

字符串哈希

核心思想:将字符串看成P进制数,P的经验值是131或13331,取这两个值的冲突概率低
小技巧:取模的数用2^64,这样直接用unsigned long long存储,溢出的结果就是取模的结果
 
typedef unsigned long long ULL;
ULL h[N], p[N]; // h[k]存储字符串前k个字母的哈希值, p[k]存储 P^k mod 2^64
 
// 初始化
p[0] = 1;
for (int i = 1; i <= n; i ++ )
{
    h[i] = h[i - 1] * P + str[i];
    p[i] = p[i - 1] * P;
}
 
// 计算子串 str[l ~ r] 的哈希值
ULL get(int l, int r)
{
    return h[r] - h[l - 1] * p[r - l + 1];
}

查询子串再减去子串中的一个字符

int get_s(int l, int r, int x)
//去掉x位置的字符得到的字符串的hash值
{
    return get_hash(l, x-1) * p[r-x] + get_hash(x+1, r);
}

查询两个子串拼接的hash

int get_s1_s2(int l1, int r1, int l2, int r2){
    return get_hash(l1, r1) * p[r2-l2+1] + get_hash(l2, r2);
}

KMP——字符串匹配

例题
KMP最小循环节、循环周期:

定理:假设S的长度为 len,则S存在最小循环节,循环节的长度L为 len-next[len] ,子串为S[0…len-next[len]-1]。

(1)如果 len 可以被 len - next[len] 整除,则表明字符串S可以完全由循环节循环组成,循环周期 T=len/L。

(2)如果不能,说明还需要再添加几个字母才能补全。需要补的个数是循环个数 L-len%L=L-(len-L)%L=L-next[len]%L,L=len-next[len]。

(3) 循环节出现的长度就是当前位置的长度

模板题

void getnext(){
    int j=0,k=-1;
    ne[0]=-1;
    while(j<lena){
        if(k==-1||a[j]==a[k]){
            j++;
            k++;
            ne[j]=k;
        }
        else{
            k=ne[k];
        }
    }
}
int kmp(){
    int i=0,j=0;
    int ans=0;
    getnext();
    while(j<lenb){
        if(i==-1||a[i]==b[j]){
            i++;
            j++;
        }
        else{
            i=ne[i];
        }
        if(i==lena){
            //i=ne[i];
            ans++;
        }
    }
    return ans;
}

求循环节

int m=len-Next[len];
if(len%m==0)//这个是代表是不是真好够整个循环
cout<<m <<endl; //每个循环节的长度
cout<<len/m<<endl; //循环几次

m=lena-ne[lena],这个是循环节,
lena%m==0&&lena/m>=2这个是说正好是一个循环,并且循环长度大于2
m-lena%m//是补充几个构成循环

posted @ 2023-12-17 21:36  lipu123  阅读(17)  评论(0)    收藏  举报