HHY模板

开头

#include<bits/stdc++.h>
#define eps 1e-9
#define pb push_back
#define fi first
#define se second
#define endl '\n'
#define io ios::sync_with_stdio(false); cin.tie(0); cout.tie(0)
#define db1(x) cout << #x << " = " << x << endl
#define db2(x, y) cout << #x << " = " << x << " " << #y << " = " << y << endl
#define gg cout << "---------------------\n"
#pragma GCC optimize(2) // fast 万一呢
typedef long long ll;
typedef pair<int, int> pii;
//  clock_t c1 = clock();
//  std::cerr << "Time:" << clock() - c1 << "ms";
//  freopen("in.txt","r",stdin);
//  freopen("out.txt","w",stdout);

结构体重载小于号

struct node{
    int x, id;
    // 使用sort排序时按x从大到小排序, 在优先队列中相反
    bool operator<(const node &a)const{
        return x>a.x;
    }
};

快读

inline ll rd() {
    ll 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;
}
inline void out(int N){
	if (N < 0){
		putchar('-');
		N = -N;
	}
	if (N >= 10){
		out(N / 10);
	}
	putchar(N % 10 + '0');
}

数论模板

const ll maxn = 1e5;
ll fac[maxn], facinv[maxn], inv[maxn];
const ll mod = 1e9 + 7;
ll MOD(ll a, ll m) {  // 取余
    a %= m;
    return a >= 0 ? a : a + m;
}
 
// m为质数时, a的逆元为 a^m-2。
ll inverse(ll a, ll m) {  // 求a mod m时的逆元
    a = MOD(a, m);
    if (a <= 1) return a;
    return MOD((1 - inverse(m, a) * m) / a, m);
}
 
ll gcd(ll x, ll y){
    return x%y == 0 ? y:gcd(y, x%y);
}
ll gcd(ll a, ll b) {  // 最大公约数
    a = abs(a), b = abs(b);
    while (b != 0) {
        a %= b;
        swap (a, b);
    }
    return a;
}
//扩展欧几里得,求x,y 使得 ax+by=gcd(a,b)
int exgcd(ll a, ll b, ll &x, ll &y) {
    if (!b){
        x = 1; y = 0;
        return a;
    }
    int d = exgcd(b, a % b, y, x);
    y -= (a/b) * x;
    return d;
}
//快速幂
ll fast(ll a, ll b, ll mod) {
    a %= mod;
    if (b < 0)a = inverse (a, mod), b = -b;
    ll ans = 1;
    while (b) {
        if (b & 1)ans = ans * a % mod;
        a = a * a % mod;
        b /= 2;
    }
    return ans % mod;
}

ll fac[maxn], facinv[maxn], inv[maxn];
void init() {
    fac[0] = 1;
    for (int i = 1; i < maxn; ++i)
        fac[i] = i * (fac[i - 1]) % mod;
    facinv[maxn - 1] = inverse(fac[maxn - 1], mod);
    for (int i = maxn - 2; i >= 0; --i){
        facinv[i] = facinv[i + 1] * (i + 1) % mod;
    }
    inv[1] = 1;
    for (int i = 2; i < maxn; i++)
        inv[i] = (mod - (ll) mod / i * inv[mod % i] % mod);
}
//求组合数 c[n,m],调用之前需要先调用init().
ll combi(int n, int m) {
    if (n < 0 || m < 0 || n < m)return 0;
    return fac[n] * facinv[m] % mod * facinv[n - m] % mod;
}

欧拉定理 求 a^b mod c

很显然,当b大到一定程度时,利用pow或者快速幂这样的算法是无法在给定时间内求解的,这时我们引入欧拉降幂算法,这个算法的特点就是降低幂方的值而不影响最终结果,使我们解决问题的时间缩短。

公式
在这里插入图片描述

其中φ(x)是欧拉函数的值,说简单了就是小于等于n的与n互质的数的个数,比如如果n时质数,则欧拉函数的值为n-1。

// 求单个数的欧拉函数
int phi(int n){
	int ans = n;
	for(int i = 2; i<=sqrt(n); i++){
		if(n%i == 0){
			ans = ans / i*(i-1);
			while(n%i == 0) n/=i;
		}
	}
	if(n > 1) ans = ans / n*(n-1);
	return ans;
}

// 打表求欧拉函数
void euler()  {  
    for(int i=2;i<maxn;i++){  
        if(!E[i])  
        for(int j=i;j<maxn;j+=i){  
            if(!E[j])E[j]=j;  
            E[j]=E[j]/i*(i-1);  
        }  
    }  
}

费马小定理

如果p是一个质数,而整数a不是p的倍数,则有a^(p-1)≡1(mod p)。
由费马小定理可以推出 a 的逆元就是 a^(p-2)。采用快速幂求得即可、

//1、线性求逆元
int inv[MAXN];
void INV(int a,int p)//线性求到a的逆元
{
    inv[1] = 1;
    for (int i=2; i<=a; ++i)
        inv[i] = (-(p/i))*inv[p%i]%p;
}

//2、单独求某个值的逆元
int INV(int a, int p)//线性求a的逆元
{
     if (a==1) return 1;
     return ((-(p/a)*INV(p%a))%p);
}

求正整数约数个数

任意一个正整数 X 都可以表示成若干个质数乘积的形式,即 X = p1^α1 ∗ p2 ^α2 …… ∗ pk ^ αk

则这个数的约数个数为 = (a1 + 1)(a2 + 1)……(ak + 1)

分数取模

(b/a)%p= b∗ a^(p−2) %p

求l-r的异或和

ll fun(ll x){
    if(x%4==0)      return x;
    else if(x%4==1) return 1;
    else if(x%4==2) return x+1;
    else if(x%4==3) return 0;
}
ll myxor(ll l,ll r){
    return fun(r)^fun(l-1);
}

逆元

//1、线性求逆元
int inv[maxn];
void INV(int a,int p)//线性求到a的逆元
{
    inv[1] = 1;
    for (int i=2; i<=a; ++i)
        inv[i] = (-(p/i))*inv[p%i]%p;
}

//2、单独求某个值的逆元
int INV(int a)//线性求a的逆元
{
     if (a==1) return 1;
     return ((-(p/a)*INV(p%a))%p);
}

素数筛

bool vis[maxn];
int prime[maxn],x;
void isprime(int n) {   //埃氏筛 
    for(int i=2;i<=n;i++){
        if(!vis[i]) prime[x++]=i;
        for(int j=2;j*i<=n;j++){
            vis[i*j]=true;
        }
    }
}
void oulasai(int n) {   //欧拉筛
    for(int i=2;i<=n;i++){
        if(!vis[i]) prime[x++]=i;
        for(int j=0;j<x;j++){
            if(i*prime[j]>n) break;
            vis[i*prime[j]]=true;
            if(i%prime[j]==0) break;
        }
    }
}

int vis[maxn];
void isprime(int n) {   //埃氏筛 求每个数的最小质因数。
    for(int i=2;i<=n;i++){
        if(!vis[i]) prime[x++]=i, vis[i] = i;
        for(int j=2;j*i<=n;j++){
        	if(vis[i*j] == 0)
            	vis[i*j] = i;
        }
    }
}
void oulasai(int n) {   //欧拉筛求每个数的最小质因数。
    for(int i=2;i<=n;i++){
        if(!vis[i]) prime[x++]=i,vis[i] = i;
        for(int j=0;j<x;j++){
            if(i*prime[j]>n) break;
            vis[i*prime[j]]=prime[j];
            if(i%prime[j]==0) break;
        }
    }
}

FFT

const double PI = acos(-1.0);

//定义的复数结构体
struct Complex{
    double x, y; //x + yi
    Complex(double _x = 0.0, double _y = 0.0){
        x = _x;
        y = _y;
    }
    Complex operator-(const Complex &b) const {
        return Complex(x - b.x, y - b.y);
    }
    Complex operator+(const Complex &b) const {
        return Complex(x + b.x, y + b.y);
    }
    Complex operator*(const Complex &b) const {
        return Complex(x * b.x - y * b.y, x * b.y + y * b.x);
    }
};

void change(Complex y[], int len){
    int i, j, k;
    for (i = 1, j = len / 2; i < len - 1; i++){
        if (i < j)
            swap(y[i], y[j]);
        k = len / 2;
        while (j >= k) {
            j -= k; k /= 2;
        }
        if (j < k)   j += k;
    }
    return;
}
void fft(Complex y[], int len, int on)
{
    change(y, len);
    for (int h = 2; h <= len; h <<= 1){
        Complex wn(cos(-on * 2 * PI / h), sin(-on * 2 * PI / h));
        for (int j = 0; j < len; j += h) {
            Complex w(1, 0);
            for (int k = j; k < j + h / 2; k++) {
                Complex u = y[k];
                Complex t = w * y[k + h / 2];
                y[k] = u + t;
                y[k + h / 2] = u - t;
                w = w * wn;
            }
        }
    }
    if (on == -1){
        for (int i = 0; i < len; i++) {
            y[i].x /= len;
        }
    }
}
Complex a[maxn], b[maxn], c[maxn];
int main(){
	int s = 1<<20;  // s必须为2的幂次
	fft(a,s,1);fft(b,s,1);//dft
	for(int i = 0; i < s; i++)	c[i] = a[i] * b[i];
	fft(c,s,-1);
	
	for(int i=0;i<s;i++){  //还原成十进制数
        output[i] += (int)(c[i].x+0.5);//注意精度误差
	}
}

数列公式

等差数列前n项和
在这里插入图片描述
在这里插入图片描述

等比数列前n项和:
在这里插入图片描述

莫比乌斯反演

const int maxn=1e6+5;
bool vis[maxn];
int prime[maxn],mu[maxn];
void init_mu(int n){//init_mu(n) 就可得到1-n的莫比乌斯函数的值,存在mu中
    int cnt=0;
    mu[1]=1;
    for(int i=2;i<n;i++){
        if(!vis[i]){
            prime[cnt++]=i;
            mu[i]=-1;
        }
        for(int j=0;j<cnt&&i*prime[j]<n;j++){
            vis[i*prime[j]]=1;
            if(i%prime[j]==0)   {mu[i*prime[j]]=0;break;}
            else { mu[i*prime[j]]=-mu[i];}
        }
    }
}

几何模板

1.求点到线段最短距离

struct P{
    llb x, y;
};
//p1、p2组成的线段到点p的最短距离
llb get(P p, P p1, P p2)
{
    llb dis = 0.0;
    llb dx = p2.x - p1.x;
    llb dy = p2.y - p1.y;
  
    // 两直线垂直,向量表示法,转换后公示
    llb k = -((p1.x - p.x)*dx + (p1.y - p.y)*dy) / ( dx*dx + dy*dy);
    llb footX = k*dx + p1.x;
    llb footY = k*dy + p1.y;
  
    //if垂足是否落在线段上
    if( footY >= min(p1.y, p2.y) && footY <=max(p1.y, p2.y)
        && footX >= min(p1.x, p2.x) && footX <=max(p1.x, p2.x ) ){
        dis = sqrt((footX-p.x)*(footX-p.x) + (footY-p.y)*(footY-p.y));
    }
    else{
        llb dis1 = sqrt((p1.x-p.x)*(p1.x-p.x) + (p1.y-p.y)*(p1.y-p.y));
        llb dis2 = sqrt((p2.x-p.x)*(p2.x-p.x) + (p2.y-p.y)*(p2.y-p.y));
        dis = ( dis1 < dis2 ? dis1 : dis2 );
    }
    return dis;
}

最长上升子序

int arr[N], n, lis[N];
// 最长上升子序列 nlongn 
int Lis(){
	int len = 0;
	// 以1为起始 
	lis[++len] = arr[1];
    for(int i = 2; i <= n; i++){
        if(arr[i] > lis[len])
            lis[++len] = arr[i];
        else{
            int pos = lower_bound(lis+1, lis+1+len, arr[i])-lis;
            lis[pos] = arr[i];
        }
    }
    return len;
}
// 下降
bool cmp(int a, int b){
	return a > b;
}

int Lis1(){
	memset(lis,0,sizeof(lis));
	int len = 0;
	// 以1为起始 
	lis[++len] = arr[1];
    for(int i = 2; i <= n; i++){
        if(arr[i] < lis[len])
            lis[++len] = arr[i];
        else{
            int pos = lower_bound(lis+1, lis+1+len, arr[i], cmp)-lis;
            lis[pos] = arr[i];
        }
    }
    return len;
}

二维差分

//在左上角是 (x1,y1),右下角是 (x2,y2) 的矩形区间每个值都 +c
void updata(int x1, int y1, int x2, int y2, int c){
	dif[x1][y1] += c;
	dif[x1][y2+1] -=c;
	dif[x2+1][y1] -=c;
	dif[x2+1][y2+1] += c;
}

// 由原始矩阵得到前缀和矩阵 
sum[i][j] = sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1] + arr[i][j]; 
// 原始矩阵得到差分矩阵 
dif[i][j] = arr[i][j] - arr[i-1][j] - arr[i][j-1] + arr[i-1][j-1];

离散化

vector<int> alls; // 存储所有待离散化的值
sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end());   // 去掉重复元素

// 二分求出x对应的离散化的值
int find(ll x) // 找到第一个大于等于x的位置
{
    int l = 0, r = alls.size() - 1;
    while (l < r){
        int mid = l + r >> 1;
        if (alls[mid] >= x) r = mid;
        else l = mid + 1;
    }
    return r + 1; // 映射到1, 2, ...n
}

字符串哈希

核心思想:将字符串看成P进制数,P的经验值是13113331,取这两个值的冲突概率低
小技巧:取模的数用2^64,这样直接用unsigned long long存储,溢出的结果就是取模的结果

typedef unsigned long long ULL;
const int maxn = 2e6+10;
const int P = 13331;
ULL h1[maxn], h2[maxn], p[maxn]; // h[k]存储字符串前k个字母的哈希值, p[k]存储 P^k mod 2^64
// 字符串拼接时注意前面的字符串要乘以对应的 p[后面字符串的个数]
// 初始化
p[0] = 1;
//下标从1开始, 如果是字符串(下标从0开始)使用get时get(l,r)对应[l-1,r-1]
for (int i = 1; i <= str.size(); i ++ ){
    h1[i] = h1[i - 1] * P + str[i-1];
    p[i] = p[i - 1] * P;
}
// l<r 求回文串时 反方向
for (int i = str.size(); i >= 1; i--){
	h2[i] = h2[i + 1] * P + str[i-1];
}
// 计算子串 str[l-1 ~ r-1] 的哈希值
ULL get1(int l, int r){
    return h1[r] - h1[l - 1] * p[r - l + 1];
}
// l<r 求回文串时 反方向 
// 比如abcde get2(1,3)求的是字符串cba的值。get(3,5)求的是edc的值。
ULL get2(int l, int r){
    return h2[l] - h2[r + 1] * p[r - l + 1];
}
// 取模版
const int mod = 1e9+7;
p[0] = 1;
for(int i = 1; i <= str.size(); i++){
	h1[i] = (h1[i-1]*P%mod + str[i-1]) % mod;
	p[i] = (p[i-1]*P) % mod;
}
for(int i = str.size(); i >= 1; i--){
	h2[i] = ( h2[i+1]*P%mod + str[i-1]) % mod;
}
ll get1(int l, int r){
	return (h1[r] - (h1[l-1] * p[r-l+1]%mod)%mod + mod) % mod;
}
ll get2(int l, int r){
	return (h2[l] - (h2[r+1] * p[r-l+1]%mod)%mod + mod) % mod ;	
}

字符串最小表示法

// 获得一个数组的最小表示法 
int N = 6;   // 数组长度 
void get_min(int arr[]){
	int w[N*2];
	for(int i = 0; i < N*2; i++){
		w[i] = arr[i%N];
	}
	// i 与 j 不能相同 
	int i = 0, j = 1, k = 0;
	while(i<=N&&j<=N){
		for(k = 0; k<N&&w[i+k]==w[j+k]; k++);
		if(k == N) break;
		if(w[i+k] > w[j+k]){
			i+=k+1; if(i==j) i++;
		}else{
			j+=k+1; if(i==j) j++;
		}
	}
	//db2(i,j);
	i = min(i, j);
	for(int k = 0; k<N; k++,i++){
		arr[k] = w[i];
		//cout << arr[k] << " ";
	}//cout << endl;
}

位运算技巧

&	与	    两个位都为1 时,结果才为1
|	或	    两个位都为0 时,结果才为0
^	异或	两个位相同为0,相异为1
~	取反	0110
<<	左移	各二进位全部左移若干位,*2
>>	右移	各二进位全部右移若干位,/2

返回n的最后一位1lowbit(n) = n & -n
获得第i位的数字: (a>>i)&1 或者 a&(1<<i)
设置第i位为1:    a=a|(1<<i)  //i从0开始
设置第i位为0:    a=a&(~(1<<i))
把第i位取反:     a=a^(1<<i)
获取数字二进制位的的个数: __builtin_popcount(int num)

数据结构

单调栈

class MinStack {
public:
    /** initialize your data structure here. */
    // 最小栈, 可改为最大栈
    stack<int> s1, s2;
    MinStack() {
        
    }
    void push(int x) {
        s1.push(x);
        if(s2.empty()|| x <= s2.top()){
            s2.push(x);
        }
    }
    void pop() {
        if(s1.top() == s2.top()) s2.pop();
        s1.pop();
    }
    int top() {
        return s1.top();
    }
    int getMin() {
        return s2.top();
    }
};


并查集

int find(int x){
    return x == fa[x] ? x : (fa[x] = find(fa[x]));
}
void merge(int i, int j){
    fa[find(i)] = find(j);
}

Trie树

字符串字典树

int son[N][26], cnt[N], idx;
// 0号点既是根节点,又是空节点
// son[][]存储树中每个节点的子节点
// cnt[]存储以每个节点结尾的单词数量

// 插入一个字符串
void insert(string str){
    int p = 0;
    for (int i = 0; str[i]; i ++ ){
        int u = str[i] - 'a';
        if (!son[p][u]) son[p][u] = ++ idx;
        p = son[p][u];
    }
    cnt[p] ++ ;
}

// 查询字符串出现的次数
int query(string str){
    int p = 0;
    for (int i = 0; str[i]; i ++ ){
        int u = str[i] - 'a';
        if (!son[p][u]) return 0;
        p = son[p][u];
    }
    return cnt[p];
}

数字字典树(求最大异或和)

int son[N*31][2], idx;
int arr[N];
// 0号点既是根节点,又是空节点, 使用前最好把0点先插入进去
// son[][]存储树中每个节点的子节点

// 插入一个数 
void insert(int x){
    int p = 0;
    // 从最高位开始 
    for(int i = 31; i >= 0; i--){
    	int u =  x >> i & 1;
    	if(son[p][u] == 0) son[p][u] = ++idx;
    	p = son[p][u];
	}
}

// 查找 
int query(int x){
	int p = 0, ans = 0;
	for(int i = 31; i >= 0; i--){
		int u = x >> i & 1;
		if(son[p][!u] == 0){  //与该数相反的数不存在 
			p = son[p][u];
			ans = ans * 2 + u;  //计算这个数的值。 
		}else{
			p = son[p][!u];
			ans = ans * 2 + !u;
		}
	}
	return ans^x;  // 返回异或结果 
}

树状数组

单点修改 + 区间查询

ll sum[maxn];

void add(int p, ll x){ //给位置p增加x
    while(p <= n) sum[p] += x, p += p & -p;
}
ll ask(ll p){ //求位置p的前缀和
    ll res = 0;
    while(p) res += sum[p], p -= p & -p;
    return res;
}
ll range_ask(int l, int r){ //区间求和
    return ask(r) - ask(l - 1);
}

区间修改 + 单点查询

ll sum[maxn];
void add(int p, ll x){    //这个函数用来在树状数组中直接修改
    while(p <= n) sum[p] += x, p += p & -p;
}
void range_add(int l, int r, ll x){  //给区间[l, r]加上x
    add(l, x), add(r + 1, -x);
}
ll ask(int p){  //单点查询(查询,经过区间修改后该点变化的值)
    ll res = 0;
    while(p) res += sum[p], p -= p & -p;
    return res;
}

区间修改 + 区间查询

ll sum1[maxn], sum2[maxn];
void add(ll p, ll x){
    for(int i = p; i <= n; i += i & -i)
        sum1[i] += x, sum2[i] += x * p;
}
void range_add(ll l, ll r, ll x){
    add(l, x), add(r + 1, -x);
}
ll ask(ll p){
    ll res = 0;
    for(int i = p; i; i -= i & -i)
        res += (p + 1) * sum1[i] - sum2[i];
    return res;
}
ll range_ask(ll l, ll r){
    return ask(r) - ask(l - 1);
}

求逆序对

int a[maxn];
int lowbit(int x){return x&(-x);}
void update(int p){
    while(p<=n) {
        a[p] ++;
        p+=lowbit(p);
    }
}
int getsum(int p){
    int res = 0;
    while(p)
        res += a[p],p -= lowbit(p);
    return res;
}
ll res = 0;  // a的逆序对个数为:
for(int i=1;i<=n;i++){
    update(b[i]+1);
    res += i-getsum(b[i]+1);
}

求区间最大值


const int MAXN = 3e5;
int a[MAXN], h[MAXN];
int n, m;
 // a数的值, h区间范围的的最大值
int lowbit(int x){
	return x & (-x);
}
void updata(int x){
	int lx, i;
	while (x <= n){
		h[x] = a[x];
		lx = lowbit(x);
		for (i=1; i<lx; i<<=1)
			h[x] = max(h[x], h[x-i]);
		x += lowbit(x);
	}		
}
int query(int x, int y){
	int ans = 0;
	while (y >= x){
		ans = max(a[y], ans);
		y --;
		for (; y-lowbit(y) >= x; y -= lowbit(y))
			ans = max(h[y], ans);
	}
	return ans;
}

二维树状数组

void add(int x,int y,ll v)
{
    for(int i=x;i<=n;i+=lowbit(i))
        for(int j=y;j<=m;j+=lowbit(j))
        {
            t1[i][j]+=v;
            t2[i][j]+=v*x;
            t3[i][j]+=v*y;
            t4[i][j]+=v*x*y;
        }
}
void real_add(int x1,int y1,int x2,int y2,ll v)
{
    add(x1,y1,v);
    add(x1,y2+1,-v);
    add(x2+1,y1,-v);
    add(x2+1,y2+1,v);
}
ll ask(int x, int y)
{
    ll res=0;
    for(int i=x;i;i-=lowbit(i))
        for(int j=y;j;j-=lowbit(j))
            res+=(x+1)*(y+1)*t1[i][j]-(y+1)*t2[i][j]-(x+1)*t3[i][j]+t4[i][j];
    return res;
}
ll real_ask(int x1,int y1,int x2,int y2)
{
    return ask(x2,y2)-ask(x2,y1-1)-ask(x1-1,y2)+ask(x1-1,y1-1);
}

线段树

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAXN = 1e5 + 5;
ll tree[MAXN << 2], mark[MAXN << 2], n, m, A[MAXN];
void push_down(int p, int len)
{
    if (len <= 1) return;
    tree[p << 1] += mark[p] * (len - len / 2);
    mark[p << 1] += mark[p];
    tree[p << 1 | 1] += mark[p] * (len / 2);
    mark[p << 1 | 1] += mark[p];
    mark[p] = 0;
}
void build(int p = 1, int cl = 1, int cr = n)
{
    if (cl == cr) return void(tree[p] = A[cl]);
    int mid = (cl + cr) >> 1;
    build(p << 1, cl, mid);
    build(p << 1 | 1, mid + 1, cr);
    tree[p] = tree[p << 1] + tree[p << 1 | 1];
}
ll query(int l, int r, int p = 1, int cl = 1, int cr = n)
{
    if (cl >= l && cr <= r) return tree[p];
    push_down(p, cr - cl + 1);
    ll mid = (cl + cr) >> 1, ans = 0;
    if (mid >= l) ans += query(l, r, p << 1, cl, mid);
    if (mid < r) ans += query(l, r, p << 1 | 1, mid + 1, cr);
    return ans;
}
void update(int l, int r, int d, int p = 1, int cl = 1, int cr = n)
{
    if (cl >= l && cr <= r) return void(tree[p] += d * (cr - cl + 1), mark[p] += d);
    push_down(p, cr - cl + 1);
    int mid = (cl + cr) >> 1;
    if (mid >= l) update(l, r, d, p << 1, cl, mid);
    if (mid < r) update(l, r, d, p << 1 | 1, mid + 1, cr);
    tree[p] = tree[p << 1] + tree[p << 1 | 1];
}
int main()
{
    ios::sync_with_stdio(false);
    cin >> n >> m;
    for (int i = 1; i <= n; ++i)
        cin >> A[i];
    build();
    while (m--)
    {
        int o, l, r, d;
        cin >> o >> l >> r;
        if (o == 1)
            cin >> d, update(l, r, d);
        else
            cout << query(l, r) << '\n';
    }
    return 0;
}

ST表

(求区间最大最小值)预处理 O(nlogn), 查询O(1)

int logn[maxn], fmx[maxn][21], fmi[maxn][21], arr[maxn];

// 初始化st表
fmx[i][0] = fmi[i][0] = arr[i];
for (int i = 1; i <= 20; ++i)
   for (int j = 1; j + (1 << i) - 1 <= n; ++j){
    	fmx[j][i] = max(fmx[j][i - 1], fmx[j + (1 << (i - 1))][i - 1]);
    	fmi[j][i] = min(fmi[j][i - 1], fmi[j + (1 << (i - 1))][i - 1]);
	}
}

//初始化log
void pre(){
	logn[1] = 0, logn[2] = 1;
	for(int i = 3; i < maxn; i++) logn[i] = logn[i/2]+1; 
}

// 求 [l,r]区间最大值 
int findmax(int l, int r){
	int s = logn[r-l+1];
	return max(fmx[l][s], fmx[r-(1<<s)+1][s]);
}

int findmin(int l, int r){
	int s = logn[r-l+1];
	return min(fmi[l][s], fmi[r-(1<<s)+1][s]);
} 

后缀数组nlogn

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100010;
int wa[N],wb[N],wv[N],wss[N],rak[N],height[N],cal[N],n,sa[N];
char s[N];
int cmp(int *r,int a,int b,int l)
{return r[a]==r[b]&&r[a+l]==r[b+l];}
void da(int *r,int *sa,int n,int M) {
     int i,j,p,*x=wa,*y=wb,*t;
     for(i=0;i<M;i++) wss[i]=0;
     for(i=0;i<n;i++) wss[x[i]=r[i]]++;
     for(i=1;i<M;i++) wss[i]+=wss[i-1];
     for(i=n-1;i>=0;i--) sa[--wss[x[i]]]=i;
     for(j=1,p=1;p<n;j*=2,M=p) {
        for(p=0,i=n-j;i<n;i++) y[p++]=i;
        for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;
        for(i=0;i<n;i++) wv[i]=x[y[i]];
        for(i=0;i<M;i++) wss[i]=0;
        for(i=0;i<n;i++) wss[wv[i]]++;
        for(i=1;i<M;i++) wss[i]+=wss[i-1];
        for(i=n-1;i>=0;i--) sa[--wss[wv[i]]]=y[i];
        for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)
        x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
     }
     return;
}
void calheight(int *r,int *sa,int n) {
     int i,j,k=0;
     for(i=1;i<=n;i++) rak[sa[i]]=i;
     for(i=0;i<n;height[rak[i++]]=k)
     for(k?k--:0,j=sa[rak[i]-1];r[i+k]==r[j+k];k++);
     for(int i=n;i;i--)rak[i]=rak[i-1],sa[i]++;
}
int main(){
    int cas=1;
    while(~scanf("%s",s+1)){
        n=strlen(s+1);
        for(int i=1;i<=n;i++)
            cal[i]=s[i];
        cal[n+1]=0;
        //200是字符ASCII码大小范围 
        da(cal+1,sa,n+1,200);
        calheight(cal+1,sa,n);
        for(int i=1;i<=n;i++) {
            printf("%d ",height[i]);
        }puts("");
        for(int i=1;i<=n;i++) {
            printf("%d ",sa[i]);
        }puts("");
        for(int i=1;i<=n;i++) {
            printf("%d ",rak[i]);
        }puts("");
    }
}

莫队

const int maxn = 1e5+10;
struct ques{
    int id,l,r; 
}q[100010];//结构体存每个询问,注意要记录询问顺序以便输出
const int block=1300;// 分的块大小 或者为sqrt(n) 

int a[maxn*2],ans[maxn],res=0;//a数组数据,ans存各个询问答案,res当前左右端点答案
int vis[maxn];
void add(int pos){
    //视情况,略..这里为统计区间不同数的个数 
	if(vis[a[pos]] == 0) res++;
	vis[a[pos]]++;
}
void del(int pos){
    //视情况,略..
	if(vis[a[pos]] == 1) res--; 
	vis[a[pos]]--;
}
//排序比较函数
bool cmp(ques a,ques b){
	return a.l/block==b.l/block?a.r<b.r:a.l/block<b.l/block;
}

int main(){
   	int n, m, nowl, nowr;
   	memset(vis,0,sizeof(vis));
    for(int i=1; i<=n; i++)
        scanf("%d", &a[i]);
    for(int i=1; i<=m; i++){
    	scanf("%d%d", &q[i].l,&q[i].r); //读入数据及询问
		q[i].id=i;    
	}   
    res = 0; nowl=0; nowr=0;       //当前左右端点
    sort(q+1, q+m+1, cmp);  //给询问排序
    for(int i=1; i<=m; i++){
        while(nowl<q[i].l) del(nowl++);
        while(nowl>q[i].l) add(--nowl);
		while(nowr<q[i].r) add(++nowr);
        while(nowr>q[i].r) del(nowr--);
        ans[q[i].id]=res;//存下该次询问答案
    }
    // 输出答案 
	for(int i = 1; i <= m; i++) printf("%d\n",ans[i]);    	
}

图论:

堆优化迪杰斯特拉

const int maxn = 2e5;
struct Node {
    int pos;
    int dis;
    Node() {}
    Node(int p, int d) {
        pos = p;
        dis = d;
    }
};
// 由于优先队列默认为大顶堆,所以重载小于号要用 a.dis > b.dis,来起到小顶堆的作用
bool operator<(const Node &a, const Node &b) {
    return a.dis > b.dis;
}

int dis[maxn];          // 距离 
vector<Node> G[maxn];   // 存图 
priority_queue<Node> que;  //优先队列 
bool st[maxn];  // 判断是否遍历过 

void dij(int s) {  // s 起始点
    // 初始化距离
    fill(dis, dis + n + 1, INT_MAX);
    dis[s] = 0;
    que.push(Node(s, 0));
    while(!que.empty()) {
        Node tmp = que.top();
        que.pop();
        if (st[tmp.pos]) continue;
        st[tmp.pos] = true;
        // 更新节点 tmp 的邻接节点,若邻接节点被更新,则将节点和 dis 加入小顶堆
        int len = G[tmp.pos].size();
        for(int i = 0; i < len; ++i) {
            int pos = G[tmp.pos][i].pos;
            int d = G[tmp.pos][i].dis;
            if(dis[pos] > tmp.dis + d) {
                dis[pos] = tmp.dis + d;
                que.push(Node(pos, dis[pos]));
            }
        }
    }
}

//堆优化,优先级队列 复杂度O(E*log(E))
struct node{
	ll v,c;
	node(ll _v=0,ll _c=0):v(_v),c(_c){}
	bool operator < (const node &r)const {return c>r.c;}
};
struct Edge{
	ll v,cost;
	Edge (ll _v=0,ll _cost=0):v(_v),cost(_cost){}
};
//vector<Edge>E[maxn];

bool vis[maxn];
ll dis[maxn];

void dij(vector<Edge>E[maxn], int n, int start)
{
	memset(vis,false,sizeof (vis));
	for(int i=0;i<=n;i++) dis[i]=inf;
	priority_queue<node>q;
	while(!q.empty()) q.pop();
	dis[start]=0;
	q.push(node(start,0));
	node x;
	while(!q.empty())
	{
		x=q.top();	q.pop();
		ll u=x.v;
		if(vis[u])continue;
		vis[u]=true;
		for(unsigned int i=0;i<E[u].size();i++){
			ll v=E[x.v][i].v;
			ll cost=E[u][i].cost;
			if(!vis[v]&&dis[v]>dis[u]+cost){
				dis[v]=dis[u]+cost;
				q.push(node(v,dis[v]));
			}
		}
	}
}

SPFA

vector<node> G[maxn];
bool inqueue[maxn];
int dist[maxn];

void Init() {  
    for(int i = 0 ; i < maxn ; ++i){  
        G[i].clear();  
        dist[i] = INF;  
    }  
}  
int SPFA(int s,int e) {  
    int v1,v2,weight;  
    queue<int> Q;  
    memset(inqueue,false,sizeof(inqueue)); // 标记是否在队列中  
    memset(cnt,0,sizeof(cnt)); // 加入队列的次数  
    dist[s] = 0;  
    Q.push(s); // 起点加入队列  
    inqueue[s] = true; // 标记  
    while(!Q.empty()){  
        v1 = Q.front();  
        Q.pop();  
        inqueue[v1] = false; // 取消标记  
        for(int i = 0 ; i < G[v1].size() ; ++i){ // 搜索v1的链表  
            v2 = G[v1][i].vex;  
            weight = G[v1][i].weight;  
            if(dist[v2] > dist[v1] + weight){ // 松弛操作  
                dist[v2] = dist[v1] + weight;  
                if(inqueue[v2] == false){  // 再次加入队列  
                    inqueue[v2] = true;  
                    //cnt[v2]++;  // 判负环  
                    //if(cnt[v2] > n) return -1;  
                    Q.push(v2);  
                } } }  
    }  
    return dist[e];  
}

/*
    不断的将s的邻接点加入队列,取出不断的进行松弛操作,直到队列为空  
    如果一个结点被加入队列超过n-1次,那么显然图中有负环  
*/

Kruskal

/*
    第一步:点、边、加入vector,把所有边按从小到大排序
    第二步:并查集部分 + 下面的code
*/

void Kruskal() {    
    ans = 0;    
    for (int i = 0; i<len; i++) {    
        if (Find(edge[i].a) != Find(edge[i].b)) {    
            Union(edge[i].a, edge[i].b);    
            ans += edge[i].len;    
        }    
    }    
}    

Prim

/*
    |适用于 稠密图 求最小生成树|
    |堆优化版,时间复杂度:O(elgn)|
*/

struct node {  
    int v, len;  
    node(int v = 0, int len = 0) :v(v), len(len) {}  
    bool operator < (const node &a)const {  // 加入队列的元素自动按距离从小到大排序  
        return len> a.len;  
    }  
};

vector<node> G[maxn];
int vis[maxn];
int dis[maxn];

void init() {  
    for (int i = 0; i<maxn; i++) {  
        G[i].clear();  
        dis[i] = INF;  
        vis[i] = false;  
    }  
}  
int Prim(int s) {  
    priority_queue<node>Q; // 定义优先队列  
    int ans = 0;  
    Q.push(node(s,0));  // 起点加入队列  
    while (!Q.empty()) {   
        node now = Q.top(); Q.pop();  // 取出距离最小的点  
        int v = now.v;  
        if (vis[v]) continue;  // 同一个节点,可能会推入2次或2次以上队列,这样第一个被标记后,剩下的需要直接跳过。  
        vis[v] = true;  // 标记一下  
        ans += now.len;  
        for (int i = 0; i<G[v].size(); i++) {  // 开始更新  
            int v2 = G[v][i].v;  
            int len = G[v][i].len;  
            if (!vis[v2] && dis[v2] > len) {   
                dis[v2] = len;  
                Q.push(node(v2, dis[v2]));  // 更新的点加入队列并排序  
            }  
        }  
    }  
    return ans; 
}  

染色法判断二分图

int bipartite(int s) {  
    int u, v;  
    queue<int>Q;  
    color[s] = 1;  
    Q.push(s);  
    while (!Q.empty()) {  
        u = Q.front();  
        Q.pop();  
        for (int i = 0; i < G[u].size(); i++) {  
            v = G[u][i];  
            if (color[v] == 0) {  
                color[v] = -color[u];  
                Q.push(v);  
            }  
            else if (color[v] == color[u])  
                return 0;  
        }  
    }  
    return 1;  
}  

匈牙利算法

时间复杂度是 O(nm), n表示点数,m 表示边数

int n1, n2;     // n1表示第一个集合中的点数,n2表示第二个集合中的点数
int h[N], e[M], ne[M], idx;     // 邻接表存储所有边,匈牙利算法中只会用到从第一个集合指向第二个集合的边,所以这里只用存一个方向的边
int match[N];       // 存储第二个集合中的每个点当前匹配的第一个集合中的点是哪个
bool st[N];     // 表示第二个集合中的每个点是否已经被遍历过

bool find(int x){
    for (int i = h[x]; i != -1; i = ne[i]) {
        int j = e[i];
        if (!st[j]){
            st[j] = true;
            if (match[j] == 0 || find(match[j])) {
                match[j] = x;
                return true;
            }
        }
    }
    return false;
}

// 求最大匹配数,依次枚举第一个集合中的每个点能否匹配第二个集合中的点
int res = 0;
for (int i = 1; i <= n1; i ++ ){
    memset(st, false, sizeof st);
    if (find(i)) res ++ ;
}

几何

凸包模板

struct lp {
    ll x, y;
    bool operator<(const lp&b) const {
        if(x == b.x) return y < b.y;
        return x < b.x;
    }
} cw[maxn],R[maxn];
ll cross(lp a, lp b, lp c) {
    return ((b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y));
}
// 求的是按逆时针方向得到的 R[]是返回的结果
int convex(int n) {
    int tn = 0, k;
    sort(cw, cw + n);
    for(int i = 0; i < n; i++) {
        while(tn > 1 && cross(R[tn - 1], cw[i], R[tn - 2]) <= 0) tn--;
        R[tn++] = cw[i];
    }
    k = tn;
    for(int i = n - 2; i >= 0; i--) {
        while(tn > k && cross(R[tn - 1], cw[i], R[tn - 2]) <= 0) tn--;
        R[tn++] = cw[i];
    }
    if(n > 1) tn--;
    return tn;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值