《算法笔记》——经典题型

文章详细探讨了n皇后问题的解决方案、坐标方向的表示方法、n阶乘的因子计算、二进制转换、矩阵快速幂算法、单调栈与队列的应用、字符串哈希技术、三分搜索和KMP算法,以及Trie数据结构和ST表在区间操作中的优化。
摘要由CSDN通过智能技术生成

经典

n皇后

int n;
bool row[N], dg[N * 2], udg[N * 2];
row[x] = dg[x + y] = udg[y - x + n] = true;

坐标的方向

一个点周围有八个方向,每个方向都可以对应一个二进制数,第一方向的左右方向对应的二进制,从右至左第一位相同,第二位不同,第三位枚举,所以对于一个方向可以快速确定他的左/右转90°的方向,

int dx[8] = {-1, -1, 0, 1, 1, 1, 0, -1};
int dy[8] = {0, 1, 1, 1, 0, -1, -1, -1};
for (int i = 0; i < 2; i ++ )
    d ^ 2 ^ (i << 2)//^2异或2表示第二位取反第一位不变,^(i<<2)表示枚举的第三位为1或0

n的阶乘里有多少个因子p

一个数的二进制末尾有多少个0,相当于这个数有多少个因子2

int f(int n,int p)
{
	int res=0;
	while(n)res+=n/p,n/=p;
	return res;
}

二进制

string s;
for (auto c: s) n = n * 2 + c - '0';//二进制转为十进制
void output(LL n)//十进制转为二进制
{
    for (int i = 31; i >= 0; i -- )//31位二进制,i表示2的i次方对应的那一位
    {
        int t = n >> i & 1;//第i位是0还是1
        cout << t;
    }
}

斐波那契数列(矩阵快速幂)

a1、a0都为1

void mul(int a[][2], int b[][2], int c[][2])//a、b矩阵相乘,结果存在c矩阵
{
    int temp[][2] = {{0, 0}, {0, 0}};
    for (int i = 0; i < 2; i ++ )
        for (int j = 0; j < 2; j ++ )
            for (int k = 0; k < 2; k ++ )
            {
                long long x = temp[i][j] + (long long)a[i][k] * b[k][j];
                temp[i][j] = x % MOD;
            }
    for (int i = 0; i < 2; i ++ )
        for (int j = 0; j < 2; j ++ )
            c[i][j] = temp[i][j];
}
int f_final(long long n)
{
    int x[2] = {1, 1};//x1
    int res[][2] = {{1, 0}, {0, 1}};//单位矩阵初始化
    int t[][2] = {{1, 1}, {1, 0}};//A
    long long k = n - 1;
    while (k)
    {
        if (k&1) mul(res, t, res);
        mul(t, t, t);//快速幂思想处理累乘
        k >>= 1;
    }
    int c[2] = {0, 0};
    for (int i = 0; i < 2; i ++ )
        for (int j = 0; j < 2; j ++ )
        {
            long long r = c[i] + (long long)x[j] * res[j][i];
            c[i] = r % MOD;
        }
    return c[0];
}

单调栈

求解a数组每个元素左边第一个小的数

  • step1 : 在栈不空的条件下,判断栈顶元素是否小于目标元素,若大于等于,则弹出
  • step2 : 栈顶元素即为当前元素的左边的第一个小的数
  • step3 :目标元素进栈
int a[N],stk[N],top;
while(stk&&stk[top]>=a[i])top--;
if(!top)cout<<-1<<" ";
else cout<<stk[top]<<" ";
stk[++top]=a[i];

单调队列

存储a数组中每k个长度的最大值和最小值

  • step1 : 在队列不为空的条件下,判断第一个元素是否在窗口内。
  • step2 : 若队尾元素大于等于目标元素则删除
  • step3 :目标元素进队。
const int N = 1010;
int q[N];//数组模拟双端队列,队列存储的是数组中元素的下标
void get_max(int a[], int b[], int tot, int k)
//对于a数组,总长为tot,每个滑动窗口长度为k,将每个滑动窗口的最大值存入b数组
//所以对于b数组,从下标k-1开始才有实际意义,b[k-1]
{
    int hh = 0, tt = -1;
    for (int i = 0; i < tot; i ++ )//遍历a数组每个元素
    {
        if (hh <= tt && q[hh] <= i - k) hh ++ ;//如果队首元素不在滑动窗口内,弹出
        while (hh <= tt && a[q[tt]] <= a[i]) tt -- ;//当队列不空,且队尾元素不大于目标元素,弹出队尾
        q[ ++ tt] = i;//放入目标元素
        b[i] = a[q[hh]];//队首元素即为当前滑动窗口的最大值
    }
}
void get_min(int a[], int b[], int tot, int k)//对于a数组,总长为tot,每个滑动窗口长度为k,将每个滑动窗口的最小值存入b数组
{
    int hh = 0, tt = -1;
    for (int i = 0; i < tot; i ++ )
    {
        if (hh <= tt && q[hh] <= i - k) hh ++ ;
        while (hh <= tt && a[q[tt]] >= a[i]) tt -- ;
        q[ ++ tt] = i;
        b[i] = a[q[hh]];
    }
}

字符串哈希

对于一个字符串,将其所有的字串转化为一串数字存储

const int N=1e5+10;
unsigned long long h[N],p[N];
char str[N];//源字符串
//用unsigned long long存储,当数据溢出的时候会发生数据截断。此时,就相当于取模了
unsigned long long get_hash(int l,int r)//源字符串中l到r区间内的子串
{
    return h[r]-h[l-1]*p[r-l+1];//位数对齐
}

int main()
{
    int n,m,px=131;//px是经验得到的数,使哈希冲突最小,也可取13331
    p[0]=1;
    scanf("%d%d%s",&n,&m,str+1);
    for(int i=1;i<=n;i++)//由于利用前缀和思想,所以角标从1开始
    {
        p[i]=p[i-1]*px;
        h[i]=h[i-1]*px+str[i];//利用了前缀和的思路
    }
    unordered_set<unsigned long long> hash;
    for (int i = 1; i + mid - 1 <= n; i ++ )//mid是字串长度
        hash.insert(get(i, i + mid - 1));//哈希表存储所有长度为mid的源字符串中的子串,每个子串都映射为一个整数
    return 0;
}

三分

下凸函数求极小值点,中间点小于等于两个端点和的一半

int l=0,r=1e9;
while(l<=r)
{
    int midl=l+(r-l)/3;
    int midr=r-(r-l)/3;
    if(get(midl)<=get(midr))r=midr-1;
    else l=midl+1;
}
printf("%lld\n",min(get(l),get(r)));

KMP

int ne[N];
char s[N];//源字符串
char p[N];//子串
/*********求解next数组*********/
void GetNextval(char* p)
{
	int pLen = strlen(p);
	ne[0] = -1;
	int k = -1;
	int j = 0;
	while (j < pLen)
	{
		//p[k]表示前缀,p[j]表示后缀  
		if (k == -1 || p[j] == p[k])
		{
			++j;
			++k;
			//较之前next数组求法,改动在下面4行
			if (p[j] != p[k])
				ne[j] = k;   //之前只有这一行
			else
				//因为不能出现p[j] = p[ next[j ]],所以当出现时需要继续递归,k = next[k] = next[next[k]]
				ne[j] = ne[k];
		}
		else
		{
			k = ne[k];
		}
	}
}
/*********KMP主算法*********/
void KmpSearch(char* s, char* p)
{
	int i = 0;
	int j = 0;
	int sLen = strlen(s);
	int pLen = strlen(p);
	while (i < sLen)
	{
		//①如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++    
		if (j == -1 || s[i] == p[j])
		{
			i++;
			j++;
		}
		else
		{
			//②如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]    
			//next[j]即为j所对应的next值      
			j = ne[j];
		}
        if(j==pLen)
		{
		    cout<<"!"<<i-j<<endl;
		    j=ne[j];
		}
	}
}

Trie

const int N = 3100010;
int n, m;
int tr[N][2];//总共N个点,每个点有两种情况,tr数组记录每个情况的下标
int cnt[N];//记录每个下标的计数,为1表示存在这个情况,为0表示不存在这个情况
int idx;//下标计算
int s[N];//记录异或数组的前缀和
void insert(int x, int v)//往trie插入新情况
{
    int p = 0;//根节点下标
    for (int i = 30; i >= 0; i -- )//循环取出x的每一位
    {
        int u = x >> i & 1;
        if (!tr[p][u]) tr[p][u] = ++ idx;//如果没有这个情况,就创建
        p = tr[p][u];//向下更新索引下标
        cnt[p] += v;//更新每个情况的计数
    }
}
int query(int x)  // 查询Trie中与x异或和的最大值
{
    int res = 0, p = 0;
    for (int i = 30; i >= 0; i -- )
    {
        int u = x >> i & 1;
        if (cnt[tr[p][!u]]) p = tr[p][!u], res = res * 2 + 1;//如果存在异或为1的情况,则更新为res*2+1
        else p = tr[p][u], res *= 2;//只存在较差情况
    }
    return res;
}
int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ )
    {
        int x;
        scanf("%d", &x);
        s[i] = s[i - 1] ^ x;//异或和的前缀和
    }
    int res = 0;  // 空数组
    insert(s[0], 1);
    for (int i = 1; i <= n; i ++ )
    {
        if (i - m - 1 >= 0) insert(s[i - m - 1], -1);//删除某个情况
        res = max(res, query(s[i]));
        insert(s[i], 1);//插入新情况
    }
    printf("%d\n", res);
    return 0;
}

ST表

对序列的区间进行操作,做到O(logn)插入,O(1)查询

#include <cmath>
int w[N];
int f[N][M];//N是序列长度,M为log2(N)+1;
void init_st()
{
    for (int j = 0; 1 << j <= n; j ++ )
        for (int i = 1; i + (1 << j) - 1 <= n; i ++ )
            if (!j) f[i][j] = w[i];
            else f[i][j] = gcd(f[i][j - 1], f[i + (1 << j - 1)][j - 1]);//gcd也可以换成min、max等操作
}
int query(int l, int r)
{
    int len = r - l + 1;
    int k = log2(len);
    return gcd(f[l][k], f[r - (1 << k) + 1][k]);
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值