经典
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]);
}