1
希望对你有帮助.|

zouyua

园龄:1年10个月粉丝:3关注:3

算法竞赛模板

1.基础算法

1.线性筛模板

int st[N], primes[N], cnt;
void get_primes(int n)
{
    for(int i = 2; i <= n; i++)
    {
        if(!st[i]) primes[cnt++] = i;
        for(int j = 0; primes[j] <= n / i; j ++)
        {
            st[primes[j]*i] = true;
            if(i % primes[j] == 0) break;
        }
    }
}

2.二/三分模板

二分

版本1

当我们将区间[l, r]划分成[l, mid]和[mid + 1, r]时,其更新操作是r = mid或者l = mid + 1;,计算mid时不需要加1。(右边满足的最小值)

C++ 代码模板:

int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    return l;
}
版本2

当我们将区间[l, r]划分成[l, mid - 1]和[mid, r]时,其更新操作是r = mid - 1或者l = mid;,此时为了防止死循环,计算mid时需要加1。(左边满足的最大值)

C++ 代码模板:

int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

三分

求凸函数和凹函数的最值

版本1 -> 实数三分
const double EPS = 1e-9;
while(r - l < EPS) {
    double lmid = l + (r - l) / 3;
    double rmid = r - (r - l) / 3;
    lans = cal(lmid),rans = cal(rmid);
    
    // 求凹函数的极小值
    if(lans <= rans) r = rmid;
    else l = lmid;
    
    // 求凸函数的极大值
    if(lans >= rans) l = lmid;
    else r = rmid;
}
// 输出 l 或 r 都可
cout << l << endl;

版本2 -> 整数三分
int l = 1,r = 100;
while(l < r) {
    int lmid = l + (r - l) / 3; // l + 1/3区间大小
    int rmid = r - (r - l) / 3;  // r - 1/3区间大小
    lans = cal(lmid),rans = cal(rmid);
    
    // 求凹函数的极小值
    if(lans <= rans) r = rmid - 1;
    else l = lmid + 1;
    
    // 求凸函数的极大值
    if(lasn >= rans) l = lmid + 1;
    else r = rmid - 1;
}
// 求凹函数的极小值
cout << min(lans,rans) << endl;
// 求凸函数的极大值
cout << max(lans,rans) << endl;

3.试除法求所有约数

vector<int> get_divisors(int x)
{
    vector<int> res;
    for (int i = 1; i <= x / i; i ++ )
        if (x % i == 0)
        {
            res.push_back(i);
            if (i != x / i) res.push_back(x / i);
        }
    sort(res.begin(), res.end());
    return res;
}

4.试除法分解质因数

void divide(int x)
{
    for (int i = 2; i <= x / i; i ++ )
        if (x % i == 0)
        {
            int s = 0;
            while (x % i == 0) x /= i, s ++ ;//合数一定会被分解成质数
            cout << i << ' ' << s << endl;
        }
    if (x > 1) cout << x << ' ' << 1 << endl;
    cout << endl;
}

2.数据结构

1.并查集

(1)朴素并查集:

struct DSU {
    std::vector<int> f, siz;
    
    DSU() {}
    DSU(int n) {
        init(n);
    }
    
    void init(int n) {
        f.resize(n);
        std::iota(f.begin(), f.end(), 0);
        siz.assign(n, 1);
    }
    
    int find(int x) {
        while (x != f[x]) {
            x = f[x] = f[f[x]];
        }
        return x;
    }
    
    bool same(int x, int y) {
        return find(x) == find(y);
    }
    
    bool merge(int x, int y) {
        x = find(x);
        y = find(y);
        if (x == y) {
            return false;
        }
        siz[x] += siz[y];
        f[y] = x;
        return true;
    }
    
    int size(int x) {
        return siz[find(x)];
    }
};

(2)维护到祖宗节点距离的并查集:

int p[N], d[N];
//p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离

// 返回x的祖宗节点,带权并查集
int find(int x)//更新到祖宗节点的距离
{
    if (p[x] != x)
    {
        int t = p[x];
        p[x] = find(p[x]);
        d[x] += d[t];
    }
    return p[x];
}

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

// 合并a和b所在的两个集合
pa = find(a), pb = find(b);//b合并到a
d[pb] = d[pa];
d[pa] += d[pb];
p[pb] = pa;
 // 根据具体问题,初始化find(a)的偏移量c

2.RMQ(ST表)--区间最值查询

区间查询用到st表,只能操作静态表,需要修改时,需要额外操作
首先定义dp[i,j]为以第i个数为起点,长度为2^j的一段区间中的最大值,显然状态转移为
dp[i,j]=max(dp[i,j1],[i+2(j1),j1]);
查询时把区间分成前后都为长度为len=r-l+1的区间,/k为区间的最大倍增数/,左边是(l,2k),右边是(r-2k+1,r)
结果为 max(dp[l][k],dp[r-(1<<j)+1],[k])
换底公式:k=log2(n)=log(n)/log(2); math里的函数
初始化

for(int j=0;j<M;j++)
	for(int i=1;i+(1<<j)-1<=n;i++)//中间是区间边界
		if(!j) dp[i][j] = w[i];
		else dp[i][j] = max(dp[i][j-1],dp[i+(1<<j-1)][j-1]);

查询

return max(dp[l][k],[r-(1<<k)+1][k]);
struct ST
{
    int n, q;
    vector<int> lg;
    vector<vector<int>> f;
    ST(int _n) : n(_n), lg(n + 3, 0), f(25, vector<int>(n + 1, 0)) {}
    void init(vector<int> &a)
    {
        for (int i = 1; i <= n; i++)
            f[0][i] = a[i];
        for (int j = 1; j <= 20; j++)
            for (int i = 1; i + (1 << j) - 1 <= n; i++)
                f[j][i] = gcd(f[j - 1][i], f[j - 1][i + (1 << (j - 1))]);
    }
    int query(int l, int r)
    {
        int len = __lg(r - l + 1);
        return gcd(f[len][l], f[len][r - (1 << len) + 1]);
    }
}

3. 树状数组板子

一般树状数组

struct BIT
{
    vector<int> tree;
    int N;
    BIT() {}
    BIT(int n){init(n);}
    void init(int n) {N = n, tree.resize(n);}
    void update(int x, int k){
        while (x < tree.size()) {
            tree[x] += k;
            x += x & -x;
        }
    }
    int query(int x){
        int res = 0;
        while (x > 0) {
            res += tree[x];
            x &= x - 1;
        }
        return res;
    }
};

4.封装线段树

1.结构体

const int mod = .....;
struct SegmentTree
{
	struct node
	{
		int l, r;
		//int val;
		int sum = 0;
		int mul = 1;
		int add = 0;
	}tr[N * 4];
    void push_up(int u)
	{
		tr[u].sum = (tr[u << 1].sum + tr[u << 1 | 1].sum) % mod;
	}
    void build(int u, int l, int r)
	{
		if(l == r)
		{
			tr[u] = {l, r,  a[l] % mod};
			return ;
		}
		tr[u] = {l, r};
		int mid = l + r >> 1;
		build(u << 1, l, mid);
		build(u << 1 | 1, mid + 1, r);
		push_up(u);
	}
    void settag(int u, int mu, int ad)//lazy标记的修改
	{
		tr[u].mul = tr[u].mul * mu % mod ;
		tr[u].add = (tr[u].add * mu + ad) % mod;
		tr[u].sum = (tr[u].sum * mu + ((tr[u].r - tr[u].l + 1) * ad) % mod) % mod;
	}
   void push_down(int u)
	{
		if(tr[u].mul != 1 || tr[u].add != 0)
		{
			settag(u << 1, tr[u].mul, tr[u].add);	
			settag(u << 1 | 1, tr[u].mul, tr[u].add);
			tr[u].mul = 1, tr[u].add = 0;
		}
	}
    void updata(int u, int x, int y, int mu, int ad)//把val改成val * mu + ad, 加法是1,k, 单点是0, k, 乘法是 k, 0
	{
		if(x <= tr[u].l && tr[u].r <= y)
		{
			settag(u, mu, ad);
			return ;
		}
		push_down(u);
		int mid = tr[u].l + tr[u].r >> 1;
		if(x <= mid) updata(u << 1, x, y, mu, ad);
		if(y > mid) updata(u << 1 | 1, x, y, mu, ad);
		push_up(u);
	}
    int query(int u, int x, int y)
	{
		if(x <= tr[u].l && tr[u].r <= y) return tr[u].sum;
		push_down(u);
		int mid = tr[u].l + tr[u].r >> 1;
		int res = 0;
		if(x <= mid) res += query(u << 1, x, y) % mod;
		if(y > mid) res += query(u << 1 | 1, x, y) % mod;
		return res % mod;
	}
}segt;
/*segt.build(1,1,n);
segt.update(1,l,r,mu,ad)//修改为 val* mu + ad;
segt.query(1,l,r);*/

2.数组

struct segtree
{
    #define ls (u << 1)
    #define rs (u << 1 | 1)
    vector<int> mx, lazy;
    vector<int> a;
    segtree(int n)
    {
        n = n * 4;
        mx.resize(n);
        lazy.resize(n);
        a.resize(n);
    }
    void push_up(int u)
	{
		mx[u] = max(mx[ls], mx[rs]);
	}
    void build(int u, int l, int r)
    {
        if(l == r)
        {
            mx[u]  = a[l];
            return ;
        }
        int mid = l + r >> 1;
		build(u << 1, l, mid);
		build(u << 1 | 1, mid + 1, r);
		push_up(u);
    }
    void settag(int u, int k)//lazy标记的修改
	{
		mx[u] += k;
        lazy[u] += k;
	}
    void push_down(int u)
	{
		if(lazy[u])
        {
            settag(ls, lazy[u]);
            settag(rs, lazy[u]);
            lazy[u] = 0;
        }
	}
    void update(int u, int l, int r, int x, int y, int k)//把val加k
	{
		if(x <= l && r <= y)
		{
			settag(u, k);
			return ;
		}
		push_down(u);
		int mid = l + r >> 1;
		if(x <= mid) update(ls, l, mid, x, y, k);
		if(y > mid) update(rs, mid + 1, r,  x, y, k);
		push_up(u);
	}
    int query(int u, int l, int r, int x, int y)
	{
		if(x <= l && r <= y) return mx[u];
		push_down(u);
		int mid = l + r >> 1;
		int res = -1;
		if(x <= mid) res = max(res, query(ls, l, mid, x, y));
		if(y > mid) res = max(res, query(rs, mid + 1, r, x, y));
		return res;
	}
};

3.杂项

结构体储存数的信息,比起数组,在操作传参时避免传参时过多而出错
struct Segment_Tree 
{
    int l,r;
    LL sum;  //这里可以维护任何满足区间加法的信息,这里就用区间求和了
    //根据情况tag标记
}tr[4 * N];   //要开四倍空间

函数1———build
void build (int u,int l,int r) //当前正在下标为u的点,这个点表示的区间是[l,r]
{ 
    if (l == r) 
    {
        tr[u] = {l,r,a[l]};/a[i]为各个点的信息
        return ;
    }
    tr[u] = {l,r};  //记得存储当前点表示的区间,否则你会调上一整天
    int mid = l + r >> 1;
    build (u << 1,l,mid),build (u << 1 | 1,mid + 1,r);  //u << 1就是u * 2,u << 1 | 1就是u * 2 + 1
    push_up (u);  //push_up函数的意思是把某一个节点的信息有他的子节点算出来
}
函数2.push_up
void push_up (int u) //这里只有区间和,区间和就是由一个点的左右子节点的和相加
{  
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;//向下递归更新
}
函数3.modify
	1.单点修改
	思路: 我们通过二分查找的形式找到要修改的点,然后把找的过程上的链都修改一下。时间复杂度 O(logn)
	void modify (int u,int x,int d) //当前这个点是下标为u的点,要把第x个数修改成d
    {   
    	if (tr[u].l == tr[u].r) 
        {
        	tr[u].sum = d;   //如果当前区间只有一个数,那么这个数一定是要修改的。
        	return ;
    	}
    	int mid = tr[u].l + tr[u].r >> 1;
    	if (x <= mid) modify (u << 1,x,d);  //如果在左边就递归修改左半边区间
    	else modify (u << 1 | 1,x,d);    //如果在右边就递归修改右半边区间
    	push_up (u)   //记得更新信息
	}
	2.区间修改
     思路: lazy标记法用它统一记录区间的修改,而不是一个个修改区间内的值
     void push_down (int u) //下传标记函数
    { 
    	auto &root = tr[u],&left = tr[u << 1],&right = tr[u << 1 | 1];  //加了引用符号只要修改这个变量就会修改他被赋值的变量
    	if (root.sum_tag) //有懒标记才下传
    	{  
        	left.tag += root.tag,left.sum += (LL)(left.r - left.l + 1) * root.tag;  //这里的子节点要加上懒标记,因为这里懒标记的意思是不包括当前节点
        	right.tag += root.tag,right.sum += (LL)(right.r - right.l + 1) * root.tag;  //同理
        	root.tag = 0;  //懒标记记得清零
    	}
	}
	void modify (int u,int l,int r,int d) //当前遍历到的点下标是u,要将区间[l,r]增加d
	{ 
    	if (l <= tr[u].l && tr[u].r <= r) 
    	{
        	tr[u].sum += (LL)(tr[u].r - tr[u].l + 1) * d;
        	tr[u].sum_tag += d;  //这里我才用了y总的懒标记的意思,一个点所有的子节点都加上d,而前一行时加上根节点的数,因为不包括根节点。
        	return ;
    	}
    	push_down (u);  //一定要分裂,只要记牢在递归左右区间之前,就要分裂
    	int mid = tr[u].l + tr[u].r >> 1;  //注意时tr[u].l和tr[u].r
    	if (l <= mid) modify (u << 1,l,r,d);  //左边有修改区间,就递归左半边
    	if (r >= mid + 1) modify (u << 1 | 1,l,r,d);  //右边有修改区间,就递归右半边
    	push_up (u);  //要记得要把这个点的信息更新一下
}
LL query (int u,int l,int r) {
    if (c[u].l >= l && c[u].r <= r) return c[u].v;
    int mid = c[u].l+c[u].r >> 1;
    LL ans = 0;
    if (l <= mid) ans = max (ans,query (u*2,l,r));
    if (r > mid) ans = max (ans,query (u*2+1,l,r));
    return ans;
}

4.重载线段树

struct Info {//一定要初始化
    ll sum;
    ll lazy = 0;
    int siz = 1;
    Info(int x) {
        sum = x;
    }
    Info() {
        sum = 0;
    }
};
Info merge(const Info &a, const Info &b) {
    Info c;

    return c;
}
struct segtree {
    #define ls (u << 1)
    #define rs (u << 1 | 1)
    int n;
    segtree(int n) {
        init(n);
    };
    vector<Info> info;
    vector<int> a;
    void init(int n) {
        this->n = n;
        info.resize(n << 2);
        a.resize(n << 1);
    }
    void push_up(int u) {
        info[u] = merge(info[ls], info[rs]);
    }
    void build(int u, int l, int r) {
        if (l == r) {
            info[u] = Info();//填值
            return ;
        }
        int mid = l + r >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
        push_up(u);
    }
    void settag(int u, int k) {//处理数据

    }
    void push_down(int u) {
        if (info[u].lazy) {
            settag(ls, info[u].lazy);
            settag(rs, info[u].lazy);
            info[u].lazy = 0;
        }
    }
    void update(int u, int l, int r, int pos, int k) {
        if (l == r) {
            info[u] = Info(k);
            return;
        }
        push_down(u);
        int mid = l + r >> 1;
        if (pos <= mid) update(ls, l, mid, pos, k);
        else update(rs, mid + 1, r, pos, k);
        push_up(u);
    }
    void update(int u, int l, int r, int x, int y, int k) {
        if (x <= l && r <= y) {
            settag(u, k);
            return;
        }
        push_down(u);
        int mid = l + r >> 1;
        if (x <= mid) update(ls, l, mid, x, y, k);
        if (mid < y) update(rs, mid + 1, r, x, y, k);
        push_up(u);
    }
    void update(int pos, int v) {
        update(1, 1, n, pos, v);
    }
    void update(int x, int y, int k) {
        update(1, 1, n, x, y, k);
    }
    Info query(int u, int l, int r, int x, int y) {
        if (x <= l && r <= y) return info[u];
        push_down(u);
        int mid = l + r >> 1;
        if (y <= mid) return query(ls, l, mid, x, y);
        else if (mid < x) return query(rs, mid + 1, r, x, y);
        else return merge(query(ls, l, mid, x, y), query(rs, mid + 1, r, x, y));
    }
    Info query(int l, int r) {
        return query(1, 1, n, l, r);
    }

};

5.重链刨分

需要结合线段是来维护要求的数据

dfs1 是为预处理出每个节点的深度,重儿子,和子树大小

dfs2 是为了处理出每个重链的dfs序,l[u] 以为u根开始的dfs序, r[u]为重链尾部的dfs序( r[u] = l[u] + siz[u] - 1);

两个dfs函数的起点都是根节点

build时改为

tr[u] = {l, r, a[id[l]]}
struct heavyPathDecomposition
{
    int n, tot, rt;
    vector<int> g[N];
    int hs[N], siz[N], dep[N], fa[N];
    int top[N], l[N], r[N];
    SegmentTree segt;
    void addedge(int u, int v)
    {
        g[u].push_back(v);
        g[v].push_back(u);
    }
	void dfs1(int u, int f)
	{
		dep[u] = dep[f] + 1, siz[u] = 1, hs[u] = -1;
		fa[u] = f;
		for(auto v : g[u])
		{
			if(v == f) continue;
			dfs1(v, u);
			siz[u] += siz[v];
			if(hs[u] == -1 || siz[v] > siz[hs[u]]) hs[u] = v;
		}
	}
    void dfs2(int u, int topu)
	{
		top[u] = topu;
		l[u] = ++ tot;
		id[tot] = u;
		if(hs[u] != -1) dfs2(hs[u], topu);
		for(auto v : g[u])
		{
			if(v == fa[u] || v == hs[u]) continue;
			dfs2(v, v);
		}
		r[u] = tot;
	}
    int qrang(int x, int y)
	{
		int res = 0;
		while(top[x] != top[y])
		{
			if(dep[top[x]] < dep[top[y]]) swap(x, y);
			res = (res + segt.query(1, l[top[x]], l[x]) % mod) % mod;
			x = fa[top[x]];
		}
		if(dep[x] < dep[y]) swap(x, y);
		res = (res + segt.query(1, l[y], l[x]) % mod) % mod;
		return res;
	}
    int uprang(int x, int y, int k)
	{
		while(top[x] != top[y])
		{
			if(dep[top[x]] < dep[top[y]]) swap(x, y);
			segt.updata(1, l[top[x]], l[x], 1, k);
			x = fa[top[x]];
		}
		if(dep[x] < dep[y]) swap(x, y);
		segt.updata(1, l[y], l[x], 1, k);
	}
    int qson(int x)
	{
		return segt.query(1, l[x], r[x]) % mod;
	}
    void upson(int x, int k)
	{
		segt.updata(1, l[x], r[x], 1, k);
	}
} hp;
/*hp.init(n);
hp.addedge(x, y);
hp.init2();
hp.uprang(x,y,k);
hp.qrang(x, y);
hp.qson(x, y)
hp.upson(int x, k)*/

6. 扫描线(二维数点问题)、

主要思想,从上到下的一个扫描线,形成的矩形为它的两个x边界和它的y和下个点的y,下面的边对应这块开始有贡献(即tag=1),上边对应这块贡献消失(tag=-1),存下每个点的信息,节点多一层,开八倍,其他二倍,和普通的线段树有一点区别

int n;
struct node
{
    int l, r;
    int cnt, len;
}tr[N << 3];
struct line
{
    int x1, x2, y;
    int tag;
    bool operator < (const line&_) const{return y < _.y;}
}L[N << 1];
int x[N << 1];
void build(int u, int l, int r)
{
    tr[u] = {l, r, 0, 0};
    if(l == r)
        return ;
    int mid = l + r >> 1;
    build(u << 1, l, mid);
    build(u << 1 | 1, mid + 1, r);
}
void push(int u)
{
    int l = tr[u].l, r = tr[u].r;
    if(tr[u].cnt) tr[u].len = x[r + 1] - x[l];
    else tr[u].len = tr[u << 1].len + tr[u << 1 | 1].len; 
}
void updata(int u, int l, int r, int tag)
{
    //if(l > tr[u].r || r < tr[u].l) return ;//不可少
    if(l <= tr[u].l && tr[u].r <= r)
    {
        tr[u].cnt += tag;
        push(u);
        return ;
    }
    //push_down(u);//每个标记独立,lazy标记不用下传
    if(l <= mid) updata(u << 1, l, r, tag);//和上面判断选一个
    if(r > mid) updata(u << 1 | 1, l, r, tag);
    push(u);
}
int sum;
void solve()
{
	cin >> n;
    for(int i = 1; i <= n; i ++)
    {
        int x1, x2, y1, y2;
        cin >> x1 >> y1 >> x2 >> y2;
        //cout << x1 << ' ' << x2 << endl;
        L[i] = {x1, x2, y1, 1};
        L[i + n] = {x1, x2, y2, -1};
        x[i] = x1, x[i + n] = x2;
    }
    n *= 2;
    sort(L + 1, L + 1 + n);
    sort(x + 1, x + 1 + n);
    //for(int i = 1; i <= 2 * n; i ++) cout << x[i] << ' '; cout << endl;
    int s = unique(x + 1, x + 1 + n) - x - 1;
    build(1, 1, s - 1);
    //cout << n << ' ' << s  << endl;
    for(int i = 1; i < n; i ++)
    {
        auto [x1, x2, yy, tag] = L[i];
        //cout << x1 << ' ' << x2 << ' ' << yy << ' ' << tag << endl;
        int l = lower_bound(x + 1, x + 1 + s, x1) - x;
        int r = lower_bound(x + 1, x + 1 + s, x2) - x;
        //cout << x1 << ' ' << l << ' ' << x2 << ' ' << r << endl;
        updata(1, l, r - 1, tag);
        sum += tr[1].len * (L[i + 1].y - L[i].y);
    }
    cout << sum << endl;
}

7.分块

初始化

const int MAXN = 1000005, SQ = 1005;
int st[SQ], ed[SQ], size[SQ], bel[MAXN], sum[SQ];
int n, sq;
// vector<int> v[SQ], 有时可能遇到查找问题,可以维护整块排序后的数组,整块进行询问时直接二分查找。
void init() // 初始化
{
    sq = sqrt(n);
    for (int i = 1; i <= sq; ++i)
    {
        st[i] = n / sq * (i - 1) + 1;
        ed[i] = n / sq * i;
    }
    ed[sq] = n;
    for (int i = 1; i <= sq; ++i)
        for (int j = st[i]; j <= ed[i]; ++j)
            bel[j] = i;
    for (int i = 1; i <= sq; ++i)
        size[i] = ed[i] - st[i] + 1;
}

区间修改

if (bel[x] == bel[y]) //当x与y在同一块内时,直接暴力修改原数组和sum数组: 
    for (int i = x; i <= y; ++i)
    {
        A[i] += k;
        sum[bel[i]] += k;
    }
for (int i = x; i <= ed[bel[x]]; ++i) //否则,先暴力修改左右两边的零散区间:
{
    A[i] += k;
    sum[bel[i]] += k;
}
for (int i = st[bel[y]]; i <= y; ++i)
{
    A[i] += k;
    sum[bel[i]] += k;
}
for (int i = bel[x] + 1; i < bel[y]; ++i) //然后对中间的整块打上标记:
    mark[i] += k;

区间查询

if (bel[x] == bel[y]) //同样地,如果左右两边在同一块,直接暴力计算区间和。
    for (int i = x; i <= y; ++i)
        s += A[i] + mark[bel[i]]; // 注意要加上标记
for (int i = x; i <= ed[bel[x]]; ++i) //否则,暴力计算零碎块:
    s += A[i] + mark[bel[i]];
for (int i = st[bel[y]]; i <= y; ++i)
    s += A[i] + mark[bel[i]];
for (int i = bel[x] + 1; i < bel[y]; ++i) //再处理整块:
    s += sum[i] + mark[i] * size[i]; // 注意标记要乘上块长

8.珂朵莉树(暴力骗分)

适用于随机生成的测试点, 适用于有推平操作, 维护区间信息, 区间第k大值, 区间和

struct node
{
    int l, r;
    multable ll v;
    node(ll l, ll r, ll v) : l(l), r(r), v(v) {};
    bool operator < (const node&_) const{return l < _.l;}
};
set<node> tr;//用set维护
auto split(ll pos)
{
auto split(ll pos)
{
    // 若不支持C++14,auto须改为set<node>::iterator
    auto it = tree.lower_bound(node(pos, 0, 0)); // 寻找左端点大于等于pos的第一个节点
    if (it != tree.end() && it->l == pos) // 如果已经存在以pos为左端点的节点,直接返回
        return it;
    it--; // 否则往前数一个节点
    ll l = it->l, r = it->r, v = it->v;
    tree.erase(it); // 删除该节点
    tree.insert(node(l, pos - 1, v)); // 插入<l,pos-1,v>和<pos,r,v>
    return tree.insert(node(pos, r, v)).first; // 返回以pos开头的那个节点的迭代器
}
void add(int l, int r, ll v)// 区间操作
{
    auto end = split(r + 1), begin() = split(l);

    for(auto it = begin(); it != end; it ++)
    {
        it->v += v;
    }
    tr.erase(begin(), end());
    tr.insert(node(l, r, v));
}
//区间第k小数
int kth(int l, int r, int k) {
    auto end = split(r + 1);
    vector<PII> rnk;
    for (auto it = split(l); it != end; ++it)
        rnk.emplace_back(it->v, it->r - it->l + 1);
    sort(rnk.begin(), rnk.end());
    int rnksz = rnk.size();
    for (int i = 0; i < rnksz; ++i) {
        k -= rnk[i].second;
        if (k <= 0) return rnk[i].first;
    }
    return -1;
}

9.莫队

一种离线的解决区间查询的算法,时间复杂度O(nn),在O(1) 时间内从[l, r] 转移到[l1, r], [l+1, r], [l, r1], [l, r+1];

int sq;
struct node
{
    int l, r, id;
    bool operator < (const node& _) const
    {
        if(l / sq != _.l / sq) return l < _.l;
        if(l / sq & 1) return r < _.r;//奇偶优化,方便转移
        return r > _.r; 
    }
}que[N];
sq = sqrt(n);
sort(que, que + q);
auto add = [&](int p)//数的种类数量
{
    if(cnt[a[p]] == 0) tot ++;
    cnt[a[p]] ++:
};
auto del = [&](int p)
{
    cnt[a[p]] --;
    if(cnt[a[p]] == 0) tot --;
};
int l = 1, r = 0;
for(int i = 0; i < q; i ++)
{
    while(l > que[i].l) add(-- l);
    while(r < que[i].r) add(++ r);
    while(l < que[i].l) del(l ++);
    while(r > que[i].r) del(r --);
    res[que[i].id] = tot;
}

10. 主席树

1.(求区间不同数的数量)

右端点是第一次出现的点, 查询时查询r版本, 从l 开始查

#include<bits/stdc++.h>

using namespace std;
using ull = unsigned long long;
using ll = long long;
using PII = pair<int,int>;
#define endl "\n"
#define pb push_back
const int N=5e6+10;
struct node
{
    int l, r, sum;
}tr[N << 5];
int a[N], root[N];
unordered_map<int, int> lst;
int idx, n, m;
int build(int l, int r) {
    int rt = ++ idx;
    if(l == r) return rt;
    int mid = l + r >> 1;
    tr[rt].l = build(l, mid);
    tr[rt].r = build(mid + 1, r);
    return rt;
}
inline int insert(int pre, int l, int r, int x, int v){
    int rt = ++ idx;
    tr[rt] = tr[pre];
    if(l == r) {
        tr[rt].sum += v;
        return rt;
    }
    int mid = l + r >> 1;
    if(x <= mid) tr[rt].l = insert(tr[pre].l, l, mid, x, v);
    else tr[rt].r = insert(tr[rt].r, mid + 1, r, x, v);
    tr[rt].sum = tr[tr[rt].l].sum + tr[tr[rt].r].sum;
    return rt;
}
inline int query(int q, int l, int r, int pos) {
    if(l == r) return tr[q].sum;
    int mid = l + r >> 1;
    int res = 0;
    if(pos <= mid) res += query(tr[q].l, l, mid, pos) + tr[tr[q].r].sum;
    else res += query(tr[q].r, mid + 1, r, pos);
    return res;
}
int main()
{
	cin >> n;
    for(int i = 1; i <= n; i ++) cin >> a[i];
    //root[0] = build(1, n);
    for(int i = 1; i <= n; i ++) {
        if(!lst[a[i]]) {
            root[i] = insert(root[i - 1], 1, n, i, 1);
        } else {
            root[i] = insert(root[i - 1], 1, n, lst[a[i]], -1);
            root[i] = insert(root[i], 1, n, i, 1);
        }
        lst[a[i]] = i;
    }
    cin >> m;
    while(m --) {
        int x, y; cin >> x >> y;
        cout << query(root[y], 1, n, x) << endl;
    }
}

2(区间范围和)

区间历史值问题, 区间第k小, 区间小于某个数的和, 动态开点的权值线段树 ----- abc339G

#include<bits/stdc++.h>
//#define int long long
using namespace std;
using ull = unsigned long long;
using ll = long long;
const int N=2e6+10;
const int INF=0x3f3f3f3f;
const int mod=998244353;
struct node {
    ll l, r;
    ll sum;
}tr[N << 5];
ll a[N], rt[N];
int n, q, idx;
ll res;
void push_up(int u) {//合并区间信息
    tr[u].sum = tr[tr[u].l].sum + tr[tr[u].r].sum;
}
int insert(int pre, int l, int r, int x){
    int u = ++ idx;
    tr[u] = tr[pre];
    if(l == r) {
        tr[u].sum += l;//权值线段树,一般加的是权值
        return u;
    }
    int mid = l + r >> 1;
    if(x <= mid) tr[u].l = insert(tr[pre].l, l, mid, x);
    else tr[u].r = insert(tr[pre].r, mid + 1, r, x);
    push_up(u);
    return u;
}
ll query(int u, int pre, int l, int r, int ql, int qr) {
    if(ql <= l && r <= qr) return tr[u].sum - tr[pre].sum;
    int mid = l + r >> 1;
    ll res = 0;
    if(ql <= mid) res += query(tr[u].l, tr[pre].l, l, mid, ql, qr);
    if(mid <  qr) res += query(tr[u].r, tr[pre].r, mid + 1, r, ql, qr);
    return res;
}
signed main()
{
    IOS;
	cin >> n;
	for(int i = 1; i <= n; i ++) cin >> a[i];
	for(int i = 1; i <= n; i ++) {
	    rt[i] = insert(rt[i - 1], 0, 1e9, a[i]);
	}
	cin >> q;
	while(q --) {
	    ll l, r, k; cin >> l >> r >> k;
	    l ^= res, r ^= res, k ^= res;
	    res = query(rt[r], rt[l - 1], 0, 1e9, 0, k);
	    cout << res << endl;
	}
	return 0; 
}

3.区间第k小

#include<bits/stdc++.h>

using namespace std;
using ull = unsigned long long;
using ll = long long;
using PII = pair<int,int>;
#define pb push_back
const int N=1e5+10;
struct node {
    int l, r, sum;
}tr[N << 5];
int rt[N], a[N];
vector<int> lsh;
int n, idx, q;
int find(int x) {
    return lower_bound(lsh.begin(), lsh.end(), x) - lsh.begin();
}
void push_up(int u) {
    tr[u].sum = tr[tr[u].l].sum + tr[tr[u].r].sum;
}
int insert(int pre, int l, int r, int x){
    int u = ++ idx;
    tr[u] = tr[pre];
    if(l == r) {
        tr[u].sum ++;
        return u;
    }
    int mid = l + r >> 1;
    if(x <= mid) tr[u].l = insert(tr[pre].l, l, mid, x);
    else tr[u].r = insert(tr[pre].r, mid + 1, r, x);
    push_up(u);
    return u;
}
int query(int u, int pre, int l, int r, int k) {
    if(l == r) {
        return l;
    }
    int sum = tr[tr[u].l].sum - tr[tr[pre].l].sum;
    int mid = l + r >> 1;
    if(k <= sum) return query(tr[u].l, tr[pre].l, l, mid, k);
    else return query(tr[u].r, tr[pre].r, mid + 1, r, k - sum);
}
int main()
{
    IOS;
	cin >> n >> q;
	for(int i = 1; i <= n ;i ++) cin >> a[i], lsh.pb(a[i]);
	sort(lsh.begin(), lsh.end());
	lsh.erase(unique(lsh.begin(), lsh.end()), lsh.end());
	for(int i = 1; i <= n; i ++) {
	    rt[i] = insert(rt[i - 1], 0, lsh.size() - 1, find(a[i]));
	}
	while(q --) {
	    int x, y, k; cin >> x >> y >> k;
	    int pos = query(rt[y], rt[x - 1], 0, lsh.size() - 1, k);
	    cout << lsh[pos] << endl;
	}
	return 0;
}

4.区间前k小的和

注意到底部是k * tr[l].val, 离散或者不离散化都行

E - Least Elements (atcoder.jp)

#include<bits/stdc++.h>
#define int long long
using namespace std;
using ull = unsigned long long;
using ll = long long;
using PII = pair<ll,ll>;
using PIII = pair<ll, pair<ll,ll>>;
#define endl "\n"
#define pb push_back
#define IOS ios::sync_with_stdio(false);cin.tie(0)
#define lowbit(x) (x) & (-x)
#define point(x) setiosflags(ios::fixed)<<setprecision(x)
const int N=2e5+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
int root[N];
map<int, int> dy;
struct node {
	int l, r;
	int cnt, sum, val;
}tr[N << 5];
int idx;
int insert(int pre, int l, int r, int x, int v) {
	int p = ++ idx;
	tr[p] = tr[pre];
	if(l == r) {
		tr[p].sum += v;
		tr[p].cnt ++;
		tr[p].val = v;
		return p;
	}
	int mid = l + r >> 1;
	if(x <= mid) tr[p].l = insert(tr[pre].l, l, mid, x, v);
	else tr[p].r = insert(tr[pre].r, mid + 1, r, x, v);
	tr[p].sum = tr[tr[p].l].sum + tr[tr[p].r].sum;
	tr[p].cnt = tr[tr[p].l].cnt + tr[tr[p].r].cnt;
	return p;
}
int query(int p, int pre, int l, int r, int k) {
	if(l == r) {
		return tr[p].val * k;
	}
	int cnt = tr[tr[p].l].cnt - tr[tr[pre].l].cnt;
	int res = 0, mid = l + r >> 1;
	if(k <= cnt) {
		res += query(tr[p].l, tr[pre].l, l, mid, k);
	} else {
		res += tr[tr[p].l].sum - tr[tr[pre].l].sum;
		res += query(tr[p].r, tr[pre].r, mid + 1, r, k - cnt);
	}
	return res;
}
signed main()
{
	int n, m, k; cin >> n >> m >> k;
	vector<ll> a(n + 1), lsh;
	//lsh.pb(-1e18);
	for(int i = 1; i <= n; i ++) {
		cin >> a[i];
		lsh.pb(a[i]);
	}
	sort(lsh.begin(),  lsh.end());
	lsh.erase(unique(lsh.begin(), lsh.end()), lsh.end());
	auto get = [&](int x) {
		return lower_bound(lsh.begin(), lsh.end(), x) - lsh.begin();
	};
	for(int i = 1; i <= n; i ++) {
		dy[get(a[i])] = a[i];
		//cout << get(a[i]) << ' ' << a[i] << endl; 
	}
	//for(int i = 0; i < lsh.size(); i ++) cout << i << ' ' << dy[i] << endl;
	for(int i = 1; i <= n; i ++) {
		root[i] = insert(root[i - 1], 0, lsh.size() - 1, get(a[i]), a[i]);
		//cout << get(a[i]) << endl;
	}
	for(int i = m; i <= n; i ++) {
		//cout << tr[root[i]].cnt << ' ' << tr[root[i - m]].cnt << endl;
		cout << query(root[i], root[i - m], 0, lsh.size() - 1, k) <<  ' ';
	}
	return 0;
}

11.平衡树

普通平衡树,基本功能是维护集合, 也能维护序列,就是BST+Heaq , 基本操作只有splitmerge

注意split有两种,按值分裂和按排名分裂

其他操作:比如插入,删除,求排名,求第k小,求第k小总和,区间反转,区间加减, 区间覆盖,区间max/min,有split 后一定有merge,一般用插入建树

线段树也能维护序列, 也能维护每个数出现的次数(权值线段树)

1.维护集合(按值分裂)

P3369 【模板】普通平衡树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

#include<bits/stdc++.h>

using namespace std;
using ull = unsigned long long;
using ll = long long;
using PII = pair<ll,ll>;
using PIII = pair<ll, pair<ll,ll>>;
#define endl "\n"
#define pb push_back
#define IOS ios::sync_with_stdio(false);cin.tie(0)
#define lowbit(x) (x) & (-x)
#define point(x) setiosflags(ios::fixed)<<setprecision(x)
const int N=2e6+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
mt19937 sj(111414);
struct {
	int ls, rs, val, key, siz;
}fhq[N];
int n, root, idx;
int xj(int v) {//新建节点
	fhq[++ idx] = {0, 0, v, (int)sj(), 1};
	return idx;
}
void push_up(int p) {//更新子树大小
	fhq[p].siz = fhq[fhq[p].ls].siz + fhq[fhq[p].rs].siz + 1;
}
void split(int p, int v, int &x, int &y){//分裂成以x, y编号为左右两颗子树,保证左子树的值都小于右子树
	if(!p) {
		x = y = 0;
		return ;
	}
	if(fhq[p].val <= v) {
		x = p;
		split(fhq[p].rs, v, fhq[p].rs, y);//因为x(左儿子)已经确定,所以去分裂右儿子,并传地址进去
	} else {
		y = p;
		split(fhq[p].ls, v, x, fhq[p].ls);//因为y(右儿子)已经确定,所以去分裂左儿子,并传地址进去
	}
	push_up(p);
}
int merge(int x, int y) {//递归合并分裂后的两颗子树
	if(!x || !y) return x + y;
	if(fhq[x].key < fhq[y].key) {//没有等于,保证全小于当前值的顺序
		fhq[x].rs = merge(fhq[x].rs, y);
		push_up(x);
		return x;
	} else {
		fhq[y].ls = merge(x, fhq[y].ls);
		push_up(y);
		return y;
	}
}
void insert(int v) {//插入后更新root
	int x, y, z;
	split(root, v, x, z);
	y = xj(v);//y为单节点树
	root = merge(merge(x, y), z);//保证x, y, z的大小严格顺序
}
void erase(int v) {
	int x, y, z;
	split(root, v, x, z);
	split(x, v - 1, x, y);
	y = merge(fhq[y].ls, fhq[y].rs);//删根, 上面split保证一定存在
	root = merge(merge(x, y), z);//保证x, y, z的大小严格顺序
}
int find_rank(int v) {//查找v的排名,相同的第一个
	int x, y;
	split(root, v - 1, x, y);
	int res = fhq[x].siz + 1;
	root = merge(x, y);
	return res;
}
int kth(int k) {//第k小值
	int u = root;
	while(u) {
		int tmp = fhq[fhq[u].ls].siz + 1;
		if(tmp == k) 
			break;
		else if(k < tmp)
			u = fhq[u].ls;
		else 
			k -= tmp, u = fhq[u].rs;
	}
	return fhq[u].val;//返回信息可以修改
}
int find_pre(int u, int v) {
	if(u == 0) return -INF;
	if(fhq[u].val < v) {
		int res = find_pre(fhq[u].rs, v);
		return res == -INF ? fhq[u].val : res;
	} else {
		return find_pre(fhq[u].ls, v);
	}
}
int find_nxt(int u, int v) {
	if(u == 0) return INF;
	if(fhq[u].val > v) {
		int res = find_nxt(fhq[u].ls, v);
		return res == INF ? fhq[u].val : res;
	} else {
		return find_nxt(fhq[u].rs, v);
	}
}
int main()
{
	IOS;
  	int n, m; cin >> n >> m;
  	for(int i = 1; i <= n; i ++) {
  		int x; cin >> x;
  		insert(x);
  	}
  	int lst = 0, res = 0;
  	while(m --) {
  		int op, x; cin >> op >> x;
  		//cout << op << ' ' << x << endl;
  		x ^= lst;
  		if(op == 1) {
  			insert(x);
  		} else if(op == 2) {
  			erase(x);
  		} else if(op == 3) {
  			lst = find_rank(x);
  			res ^= lst;
  			//cout << lst << endl;
  		} else if(op == 4) {
  			lst = kth(x);
  			res ^= lst;
  			//cout << lst << endl;
  		} else if(op == 5) {
  			lst = find_pre(root, x);
  			res ^= lst;
  			//cout << lst << endl;
  		} else {
  			lst = find_nxt(root, x);
  			res ^= lst;
  			//cout << lst << endl;
  		}
  	}
  	cout << res << endl;
    return 0;
}

2.维护序列(按排名分裂)

P3391 【模板】文艺平衡树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

#include<bits/stdc++.h>

using namespace std;
using ull = unsigned long long;
using ll = long long;
using PII = pair<ll,ll>;
using PIII = pair<ll, pair<ll,ll>>;
#define endl "\n"
#define pb push_back
#define IOS ios::sync_with_stdio(false);cin.tie(0)
#define lowbit(x) (x) & (-x)
#define point(x) setiosflags(ios::fixed)<<setprecision(x)
const int N=1e5+10;
const int INF=0x3f3f3f3f;
const int mod=1e9+7;
struct FHQ {
	int ls, rs, val, key, siz;
	bool tag = 0;
}fhq[N];
mt19937 sj(111414);
int root, idx, n , m;
void push_up(int p) {
	fhq[p].siz = fhq[fhq[p].ls].siz + fhq[fhq[p].rs].siz + 1;
}
void push_down(int p) {
	if(fhq[p].tag) {
		swap(fhq[p].ls, fhq[p].rs);
		if(fhq[p].ls) fhq[fhq[p].ls].tag ^= 1;
		if(fhq[p].rs) fhq[fhq[p].rs].tag ^= 1;
		fhq[p].tag = 0;
	}
}
int xj(int v) {
	fhq[++ idx] = {0, 0, v, (int)sj(), 1, 0};
	return idx;
}
void split(int p, int k, int &x, int &y) {
	if(!p) {
		x = y = 0;
		return ;
	}
	push_down(p);
	int tmp = fhq[fhq[p].ls].siz + 1;
	if(k == tmp) {
		x = p;
		y = fhq[p].rs;
		fhq[p].rs = 0;
	} else if(k < tmp) {
		y = p;
		split(fhq[p].ls, k, x, fhq[p].ls);
	} else {
		x = p;
		split(fhq[p].rs, k - tmp, fhq[p].rs, y);
	}
	push_up(p);
}
int merge(int x, int y) {
	if(!x || !y) return x + y;
	if(fhq[x].key < fhq[y].key) {
		push_down(x);
		fhq[x].rs = merge(fhq[x].rs, y);
		push_up(x);
		return x;
	} else {
		push_down(y);
		fhq[y].ls = merge(x, fhq[y].ls);
		push_up(y);
		return y;
	}
}
void insert(int v) {
	int x, y, z; 
	split(root, v, x, z);
	y = xj(v);
	root = merge(merge(x, y), z);

}
void reverse(int l, int r) {
	int x, y, z;
	split(root, l - 1, x, y);
	split(y, r - l + 1, y, z);
	fhq[y].tag ^= 1;
	root = merge(merge(x, y), z);
}
void dfs(int p) {
	if(!p) return ;
	push_down(p);
	dfs(fhq[p].ls);
	cout << fhq[p].val << ' ';
	dfs(fhq[p].rs);
}
int main()
{
	IOS;
    cin >> n >> m;
    for(int i = 1; i <= n; i ++) {
    	root = merge(root, xj(i));

    }
    while(m --) {
    	int l, r; cin >> l >> r;
    	reverse(l, r);
    }
    dfs(root);
    return 0;
}

3.图论

1.djikstra优化版

时间复杂度 O(mlogn), n 表示点数,m 表示边数
typedef pair<int, int> PII;

int n;      // 点的数量
int h[N], w[N], e[N], ne[N], idx;       // 邻接表存储所有边
int dist[N];        // 存储所有点到1号点的距离
bool st[N];     // 存储每个点的最短距离是否已确定
vector<int> g[N];
int dij()
{
    memset(dis,0x3f,sizeof dis);
    dis[1]=0;
    priority_queue<PII,vector<PII>,greater<PII>>q;
    q.push({dis[1],1});
    while(q.size())
    {
        int u = q.top().second;
        q.pop();

        if(st[u]) continue;
        st[u] = true;
        
        for(auto [v, w] : g[u])
        {
            if(dis[v]>dis[u]+w)
            {
                dis[v]=dis[u]+w;
                q.push({dis[v],v});
            }
        }
    }
    return dis[n];
}

2..spfa

时间复杂度 平均情况下 O(m),最坏情况下 O(nm), n 表示点数,m 表示边数
int n;      // 总点数
int h[N], w[N], e[N], ne[N], idx;       // 邻接表存储所有边
int dist[N];        // 存储每个点到1号点的最短距离
bool st[N];     // 存储每个点是否在队列中

void add(int a,int b,int c)
{
	w[idx]=c,e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int spfa()
{
	memset(dis,0x3f,sizeof dis);
	dis[1]=0;
	queue<pair<int,int>>q;
	q.push({dis[1],1});
	st[1]=true;
	while(q.size())
	{
		auto t=q.front();
		q.pop();
		int tt=t.second;
		st[tt]=false;
		for(int i=h[tt];i!=-1;i=ne[i])
		{
			int j=e[i];
			if(dis[j]>dis[tt]+w[i])
			{
				dis[j]=dis[tt]+w[i];
				if(!st[j])
				{
					st[j]=true;
					q.push({dis[j],j});
				}
			}
		}
	}
	return dis[n];
}

3.kruskal

时间复杂度是 O(mlogm), n 表示点数,m 表示边数
点的成本可以看作是点到某个某个特别点的边权
int n, m;       // n是点数,m是边数
int p[N];       // 并查集的父节点数组

struct Edge     // 存储边
{
    int a, b, w;

    bool operator< (const Edge &W)const
    {
        return w < W.w;
    }

}edges[M];

int find(int x)     // 并查集核心操作
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int kruskal()
{
    sort(edges, edges + m);

    for (int i = 1; i <= n; i ++ ) p[i] = i;    // 初始化并查集
    
    int res = 0, cnt = 0;
    for (int i = 0; i < m; i ++ )
    {
        int a = edges[i].a, b = edges[i].b, w = edges[i].w;
    
        a = find(a), b = find(b);
        if (a != b)     // 如果两个连通块不连通,则将这两个连通块合并
        {
            p[a] = b;
            res += w;
            cnt ++ ;
        }
    }
    
    if (cnt < n - 1) return INF;
    return res;

}

4.有向图的拓扑序列

序列保存在orz中
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
bool top()
{
    for(int i=1;i<=n;i++)
    {
        if(d[i]==0) q.push(i);
    }
    while(q.size())
    {
        int t=q.front();
        //cout<<t<<endl;
        orz[cnt++]=t;
        q.pop();
        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j=e[i];
            d[j]--;
            if(d[j]==0) q.push(j);
        }
    }
    if(cnt<n) return 0;
    else return 1;

}

5.floyd算法

时间复杂度是 O(n3), n 表示点数
初始化:
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            if (i == j) d[i][j] = 0;
            else d[i][j] = INF;

// 算法结束后,d[a][b]表示a到b的最短距离
void floyd()
{
    for (int k = 1; k <= n; k ++ )
        for (int i = 1; i <= n; i ++ )
            for (int j = 1; j <= n; j ++ )
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}

6.LCA--最近公共祖先

(1).向上标记法( O(n) )
(2).倍增
fa[i,j]标识从i开始,向上跳2^j步所能走到的节点,0<=j<=logn
depth[i]表示深度
步骤:
[1].先让两个点跳到同一层
[2].让两个点同时往上跳,一直跳到它们公共祖先的下一层
预处理O(nlogn)
查询O(logn)
(3).tarjan( O(n + m) )
在深度优先遍历的同时, 将所有点分成三类
[1].已经遍历过的点, 且回溯过的点
[2].正在搜索的分支
[3].还未搜索到的点

倍增法

//倍增处理
void bfs()
{
    //memset(dis, 0x3f, sizeof dis);
    memset(depth, 0x3f, sizeof depth);
    queue<int> q;
    depth[0] = 0, depth[1]=1;//并非是1,是root
    //dis[1] = 0;
    q.push(1);
    while(q.size())
    {
        int u = q.front();
        q.pop();
        for(auto &[v, w] : g[u])
        {
            if(depth[v]>depth[u] + 1)
            {
                depth[v] = depth[u] + 1;
                q.push(v);
                fa[v][0] = u;
                for(int k = 1; k <= 13; k ++)
                    fa[v][k]=fa[fa[v][k-1]][k-1];
                dis[v] = dis[u] + w;//权值, 到公共祖先的距离
            }
        }
    }
}
void dfs(int root, int fno) {
  // 初始化:第 2^0 = 1 个祖先就是它的父亲节点,dep 也比父亲节点多 1。
  fa[root][0] = fno;
  dep[root] = dep[fa[root][0]] + 1;
  // 初始化:其他的祖先节点:第 2^i 的祖先节点是第 2^(i-1) 的祖先节点的第
  // 2^(i-1) 的祖先节点。
  for (int i = 1; i < 31; ++i) {
    fa[root][i] = fa[fa[root][i - 1]][i - 1];
    cost[root][i] = cost[fa[root][i - 1]][i - 1] + cost[root][i - 1];
  }
  // 遍历子节点来进行 dfs。
  for(auto v : g[root])
  {
      if(v == fno) continue;
      cost[v[root][i]][0] = w[root][i];
    	dfs(v[root][i], root);
  }
//倍增查询
int lca(int a, int b)
{
    if(depth[a] < depth[b]) swap(a, b); //a在下面
    for(int k = 13; k >= 0; k --)
        if(depth[fa[a][k]] >= depth[b])
            a=fa[a][k];//跳到同一层
    if(a == b) return a;        
    for(int k = 13; k >=0 ; k --)
        if(fa[a][k] != fa[b][k])
            a =  fa[a][k], b = fa[b][k];//一起向上跳,跳到公共祖先的下一层
    return fa[a][0];
}
// lca。用倍增算法算取 x 和 y 的 lca 节点。
int lca(int x, int y) {
  // 令 y 比 x 深。
  if (dep[x] > dep[y]) swap(x, y);
  // 令 y 和 x 在一个深度。
  int tmp = dep[y] - dep[x], ans = 0;
  for (int j = 0; tmp; ++j, tmp >>= 1)
    if (tmp & 1) ans += cost[y][j], y = fa[y][j];
  // 如果这个时候 y = x,那么 x,y 就都是它们自己的祖先。
  if (y == x) return ans;
  // 不然的话,找到第一个不是它们祖先的两个点。
  for (int j = 30; j >= 0 && y != x; --j) {
    if (fa[x][j] != fa[y][j]) {
      ans += cost[x][j] + cost[y][j];
      x = fa[x][j];
      y = fa[y][j];
    }
  }
  // 返回结果。
  ans += cost[x][0] + cost[y][0];
  return ans;
}

tarjan

int find(int x)//合并祖先节点
{
    if(x != p[x]) p[x] = find(p[x]);
    return p[x];
} 
void tarjan(int u)//st[u]等于1,2可以直接合在一起,因为每次遍历时都会先对子节点进行标记
{
    //cout << u << endl;
    st[u] = 1;
    for(auto &[v, w] : g[u])
    {
        if(!st[v])
        {
            dis[v] = dis[u] + w;
            tarjan(v);
            p[v] = u;
        }
    }
    for(auto &[v, id] : query[u])//离线做法的体现
        if(st[v] == 2) 
            res[id] = dis[u] + dis[v] - dis[find(v)] * 2;
            //cout << id <<  ' ' << dis[u] << ' ' << dis[v] << ' ' << res[id] << endl;
    st[u] = 2;
}

重链刨分(HLD)

struct HLD {
    int n;
    std::vector<int> siz, top, dep, parent, in, out, seq;
    std::vector<std::vector<int>> adj;
    int cur;
    
    HLD() {}
    HLD(int n) {
        init(n);
    }
    void init(int n) {
        this->n = n;
        siz.resize(n);
        top.resize(n);
        dep.resize(n);
        parent.resize(n);
        in.resize(n);
        out.resize(n);
        seq.resize(n);
        cur = 0;
        adj.assign(n, {});
    }
    void addEdge(int u, int v) {
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
    void work(int root = 0) {
        top[root] = root;
        dep[root] = 0;
        parent[root] = -1;
        dfs1(root);
        dfs2(root);
    }
    void dfs1(int u) {
        if (parent[u] != -1) {
            adj[u].erase(std::find(adj[u].begin(), adj[u].end(), parent[u]));
        }
        
        siz[u] = 1;
        for (auto &v : adj[u]) {
            parent[v] = u;
            dep[v] = dep[u] + 1;
            dfs1(v);
            siz[u] += siz[v];
            if (siz[v] > siz[adj[u][0]]) {
                std::swap(v, adj[u][0]);
            }
        }
    }
    void dfs2(int u) {
        in[u] = cur++;
        seq[in[u]] = u;
        for (auto v : adj[u]) {
            top[v] = v == adj[u][0] ? top[u] : v;
            dfs2(v);
        }
        out[u] = cur;
    }
    int lca(int u, int v) {
        while (top[u] != top[v]) {
            if (dep[top[u]] > dep[top[v]]) {
                u = parent[top[u]];
            } else {
                v = parent[top[v]];
            }
        }
        return dep[u] < dep[v] ? u : v;
    }
    
    int dist(int u, int v) {
        return dep[u] + dep[v] - 2 * dep[lca(u, v)];
    }
    
    int jump(int u, int k) {//k级祖宗
        if (dep[u] < k) {
            return -1;
        }
        
        int d = dep[u] - k;
        
        while (dep[top[u]] > d) {
            u = parent[top[u]];
        }
        
        return seq[in[u] - dep[u] + d];
    }
    
    bool isAncester(int u, int v) {//u是不是v祖先
        return in[u] <= in[v] && in[v] < out[u];
    }
    
    int rootedParent(int u, int v) {
        std::swap(u, v);
        if (u == v) {
            return u;
        }
        if (!isAncester(u, v)) {
            return parent[u];
        }
        auto it = std::upper_bound(adj[u].begin(), adj[u].end(), v, [&](int x, int y) {
            return in[x] < in[y];
        }) - 1;
        return *it;
    }
    
    int rootedSize(int u, int v) {
        if (u == v) {
            return n;
        }
        if (!isAncester(v, u)) {
            return siz[v];
        }
        return n - siz[rootedParent(u, v)];
    }
    
    int rootedLca(int a, int b, int c) {
        return lca(a, b) ^ lca(b, c) ^ lca(c, a);
    }
};

7.匈牙利算法

时间复杂度O(nm),实际远小于

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

bool find(int u)
{
    for (auto v : g[u])
    {
        if (!st[v])
        {
            st[v] = true;
            if (match[v] == 0 || find(match[v]))
            {
                match[v] = u;
                return true;
            }
        }
    }

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

8.染色法判断二分图

int n;      // n表示点数
int h[N], e[M], ne[M], idx;     // 邻接表存储图
int color[N];       // 表示每个点的颜色,-1表示未染色,0表示白色,1表示黑色

// 参数:u表示当前节点,c表示当前点的颜色
bool dfs(int u, int c)
{
    color[u] = c;
    for(auto v : g[u])
    {
        if (color[v] == -1)
        {
            if (!dfs(v, !c)) return false;
        }
        else if (color[v] == c) return false;
    }

    return true;
}

bool check()
{
    memset(color, -1, sizeof color);
    bool flag = true;
    for (int i = 1; i <= n; i ++ )
        if (color[i] == -1)
            if (!dfs(i, 0))
            {
                flag = false;
                break;
            }
    return flag;
}

9.有向图的强连通分量(scc)

int dfn[N], low[N];
int stk[N], id[N], siz[N], instk[N];
int scc, top, y, tmtp;//dfs序
void targan(int u)
{
    dfn[u] = low[u] = ++ tmtp;
    instk[u] = stk[++ top] = u;
    for(auto v : g[u])
    {
        if(!dfn[v])
        {
            targan(v);
            low[u] = min(low[u], low[v]);
        }
        else if(instk[v]) low[u] = min(low[u], dfn[v]);
    }
    if(dfn[u] == low[u])
    {
        ++ scc;
        do
        {
            y = stk[top --];
            instk[y] = false;
            id[y] = scc;
            siz[scc] ++;
        }while(y != u);
    }
}

10.重构树板子(最大化路径的最小值)

最大生成树中, 求x到y的最小路径, 最小瓶颈路

int n, m;
int p[N], w[N];
int fa[N][40], val[N][40];
int dep[N];
struct nd {
    int v, w;
};
vector<PII> g[N];
struct node {
    int u, v, w;
    bool operator < (const node &_) const{
        return w > _.w;
    }
}edges[N];
int find(int x) {
    if(x != p[x]) p[x] = find(p[x]);
    return p[x];
}
void kruskal() {
    for(int i = 1; i <= n; i ++) p[i] = i;
    sort(edges + 1, edges + 1 + m);
    for(int i = 1; i <= m; i ++) {
        auto [u, v, w] = edges[i];
        int fu = find(u), fv = find(v);
        if(fu == fv) continue;
        p[fu] = fv;
        g[u].pb({v, w}), g[v].pb({u, w});
    }
}
void dfs(int u, int lst) {
    dep[u] = dep[lst] + 1;
    fa[u][0] = lst;
    //cout << u << ' ' << lst << endl;
    for(int i = 1; i <= 30; i ++) {
        val[u][i] = min(val[u][i - 1], val[fa[u][i - 1]][i - 1]);
        fa[u][i] = fa[fa[u][i - 1]][i - 1];
    }
    for(auto [v, w] : g[u]) {
        if(v == lst)  continue;
        val[v][0] = w;
        dfs(v, u);
    }
}
int lca(int a, int b) {
    int res = INF;
    if(dep[a] < dep[b]) swap(a, b);
    //cout << a << ' ' << b << endl;
    for(int k = 30; k >= 0; k --) {
        if(dep[fa[a][k]] >= dep[b]){
            res = min(res, val[a][k]);
            a = fa[a][k];
        }
            
    }
    if(a == b) return res;
    for(int k = 31; k >= 0; k --) {
        if(fa[a][k] != fa[b][k]) {
            res = min(res, val[a][k]);
            res = min(res, val[b][k]);
            a = fa[a][k], b = fa[b][k];
        }
    }
    return min({res, val[a][0], val[b][0]});
}
void solve()
{
    IOS;
	cin >> n >> m;
    for(int i = 1; i <= m; i ++) {
        int x, y, z; cin >> x >> y >> z;
        edges[i] = {x, y, z};
    }
    kruskal();
    for(int i = 1; i <= n ;i ++) {
        if(!dep[i]) {
            dfs(i, 0);
        }
    }
    int q; cin >> q;
    while(q -- ){
        int u, v; cin >> u >> v;
        if(find(u) != find(v)) cout << -1 << endl;
        else cout << lca(u, v) << endl;
    }
}

11.割边与割边缩点(EBCC)

std::set<std::pair<int, int>> E;

struct EBCC {
    int n;
    std::vector<std::vector<int>> adj;
    std::vector<int> stk;
    std::vector<int> dfn, low, bel;
    int cur, cnt;
    
    EBCC() {}
    EBCC(int n) {
        init(n);
    }
    
    void init(int n) {
        this->n = n;
        adj.assign(n, {});
        dfn.assign(n, -1);
        low.resize(n);
        bel.assign(n, -1);
        stk.clear();
        cur = cnt = 0;
    }
    
    void addEdge(int u, int v) {
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
    
    void dfs(int x, int p) {
        dfn[x] = low[x] = cur++;
        stk.push_back(x);
        
        for (auto y : adj[x]) {
            if (y == p) {
                continue;
            }
            if (dfn[y] == -1) {
                E.emplace(x, y);
                dfs(y, x);
                low[x] = std::min(low[x], low[y]);
            } else if (bel[y] == -1 && dfn[y] < dfn[x]) {
                E.emplace(x, y);
                low[x] = std::min(low[x], dfn[y]);
            }
        }
        
        if (dfn[x] == low[x]) {
            int y;
            do {
                y = stk.back();
                bel[y] = cnt;
                stk.pop_back();
            } while (y != x);
            cnt++;
        }
    }
    
    std::vector<int> work() {
        dfs(0, -1);
        return bel;
    }
    
    struct Graph {
        int n;
        std::vector<std::pair<int, int>> edges;
        std::vector<int> siz;
        std::vector<int> cnte;
    };
    Graph compress() {
        Graph g;
        g.n = cnt;
        g.siz.resize(cnt);
        g.cnte.resize(cnt);
        for (int i = 0; i < n; i++) {
            g.siz[bel[i]]++;
            for (auto j : adj[i]) {
                if (bel[i] < bel[j]) {
                    g.edges.emplace_back(bel[i], bel[j]);//缩点后的新图的边连接的两个点
                    //g.edges.emplace_back(i, j);//原图割边连接的两个点
                    
                } else if (i < j) {
                    g.cnte[bel[i]]++;
                }
            }
        }
        return g;
    }
};

12.斯坦纳树

  • 求给定连通图 G 中的 n 个点与 k 个关键点,连接 k 个关键点,使得生成树的所有边的权值和最小, 近似算法,类似最小生成树, 时间复杂度n3k+(n+m)logm

    定义dp[s][i] 为 以i为根,包含集合 s 中所有点的最小边权和,初始除了关键点全为 inf 考虑转移

    对于度数 > 1的点, $ dp[s][i] = min(dp[s][i], dp[sub][i] + dp[s - sub][i]) ,subs$的非空子集, 枚举非空子集的方法for(int sub = s; sub; sub = (sub - 1) & s)

    对于度数 = 1的点,可以用最短路进行松弛操作,类似三角不等式,
    dp[s][i] 开始跑最短路,代表当前s集合中以i为根的集合能到达的最近点
    涉及到状态压缩,建议从下标 0 开始,对于这k个点,初始状态可以定义为 dp[1<<i][key]=0 代表第key个关键点在集合内的代价为0

    G - Last Major City

signed main()
{
	int n, m, k; cin >> n >> m >> k;
	vector<vector<PII>> g(n);
	for(int i = 1; i <= m; i ++) {
		int u, v, w; cin >> u >> v >> w;
		u --, v --;
		g[u].push_back({v, w}), g[v].push_back({u, w});
	}
	vector<vector<ll>> dp(1<< (k - 1), vector<ll>(n, 1e18));
	for(int i = 0; i < k - 1 ;i ++) {
		dp[1 << i][i] = 0;
	}
	for(int s = 0; s < (1 << (k - 1)); s ++) {
		priority_queue<PII, vector<PII>, greater<>> q;
		for(int i = 0; i < n; i ++) {
			for(int sub = s; sub; sub = (sub - 1) & s) {
				dp[s][i] = min(dp[s][i], dp[sub][i] + dp[s - sub][i]);
			}
			if(dp[s][i] != 1e18) {
				q.push({dp[s][i], i});
			}
		}
		vector<int> st(n, 0);
		while(q.size()) {
			auto [d, u] = q.top(); q.pop();
			if(st[u]) continue;
			st[u] = 1;
			for(auto [v, w] : g[u]) 
				if (dp[s][v] > dp[s][u] + w) {
					dp[s][v] = dp[s][u] + w;
					q.push({dp[s][v], v});
				}
		}
	}
	for(int i = k - 1; i < n; i ++) cout << dp[(1 << (k - 1)) - 1][i] << endl;
	return 0;
}

13点的双连通分量(圆方树)

对于每一个点双连通分量,都建立一个方点,因为点双的性质,一个点双内的任意两点,都能通过点双内其他任意一点相互到达,对于割点向方点建边,方点向点双内其他所有点建边同时记录父亲,每次tajan后记得把最后一个点弹出

int fa[M], low[M], dfn[M];
int tot, tmsp, y;
vector<int> stk, g[M], h[M];
void tarjan(int u) {
    low[u] = dfn[u] = ++ tmsp;
    stk.push_back(u);
    for(auto v : g[u]) {
        if(!dfn[v]) {
            tarjan(v);
            low[u] = min(low[u], low[v]);
            if(low[v] == dfn[u]) {
                tot ++;
                do {
                    y = stk.back();
                    stk.pop_back();
                    fa[y] = tot;
                    h[tot].push_back(y);
                    h[y].push_back(tot);
                }while(v != y);
                fa[tot] = u;
                h[tot].push_back(u);
                h[u].push_back(tot);
            }
        } 
        else low[u] = min(low[u], dfn[v]);
    }
}

14.网络流

1.最大流

MaxFlow<ll> mf(n + 10);//初始化
mf.edges()//里面flow为存的残留(用过的边),cap为边容量,from, to存的为起点和终点

constexpr int inf = 1E9;
template<class T>
struct MaxFlow {
    struct _Edge {
        int to;
        T cap;
        _Edge(int to, T cap) : to(to), cap(cap) {}
    };
    
    int n;
    std::vector<_Edge> e;
    std::vector<std::vector<int>> g;
    std::vector<int> cur, h;
    
    MaxFlow() {}
    MaxFlow(int n) {
        init(n);
    }
    
    void init(int n) {
        this->n = n;
        e.clear();
        g.assign(n, {});
        cur.resize(n);
        h.resize(n);
    }
    
    bool bfs(int s, int t) {
        h.assign(n, -1);
        std::queue<int> que;
        h[s] = 0;
        que.push(s);
        while (!que.empty()) {
            const int u = que.front();
            que.pop();
            for (int i : g[u]) {
                auto [v, c] = e[i];
                if (c > 0 && h[v] == -1) {
                    h[v] = h[u] + 1;
                    if (v == t) {
                        return true;
                    }
                    que.push(v);
                }
            }
        }
        return false;
    }
    
    T dfs(int u, int t, T f) {
        if (u == t) {
            return f;
        }
        auto r = f;
        for (int &i = cur[u]; i < int(g[u].size()); ++i) {
            const int j = g[u][i];
            auto [v, c] = e[j];
            if (c > 0 && h[v] == h[u] + 1) {
                auto a = dfs(v, t, std::min(r, c));
                e[j].cap -= a;
                e[j ^ 1].cap += a;
                r -= a;
                if (r == 0) {
                    return f;
                }
            }
        }
        return f - r;
    }
    void addEdge(int u, int v, T c) {
        g[u].push_back(e.size());
        e.emplace_back(v, c);
        g[v].push_back(e.size());
        e.emplace_back(u, 0);
    }
    T flow(int s, int t) {
        T ans = 0;
        while (bfs(s, t)) {
            cur.assign(n, 0);
            ans += dfs(s, t, std::numeric_limits<T>::max());
        }
        return ans;
    }
    
    std::vector<bool> minCut() {
        std::vector<bool> c(n);
        for (int i = 0; i < n; i++) {
            c[i] = (h[i] != -1);
        }
        return c;
    }
    
    struct Edge {
        int from;
        int to;
        T cap;
        T flow;
    };
    std::vector<Edge> edges() {
        std::vector<Edge> a;
        for (int i = 0; i < e.size(); i += 2) {
            Edge x;
            x.from = e[i + 1].to;
            x.to = e[i].to;
            x.cap = e[i].cap + e[i + 1].cap;
            x.flow = e[i + 1].cap;
            a.push_back(x);
        }
        return a;
    }
};

2.费用流

边里面是(u, v, c, w)//起点,终点,容量,费用
template<class T>
struct MinCostFlow {
    struct _Edge {
        int to;
        T cap;
        T cost;
        _Edge(int to_, T cap_, T cost_) : to(to_), cap(cap_), cost(cost_) {}
    };
    int n;
    std::vector<_Edge> e;
    std::vector<std::vector<int>> g;
    std::vector<T> h, dis;
    std::vector<int> pre;
    bool dijkstra(int s, int t) {
        dis.assign(n, std::numeric_limits<T>::max());
        pre.assign(n, -1);
        std::priority_queue<std::pair<T, int>, std::vector<std::pair<T, int>>, std::greater<std::pair<T, int>>> que;
        dis[s] = 0;
        que.emplace(0, s);
        while (!que.empty()) {
            T d = que.top().first;
            int u = que.top().second;
            que.pop();
            if (dis[u] != d) {
                continue;
            }
            for (int i : g[u]) {
                int v = e[i].to;
                T cap = e[i].cap;
                T cost = e[i].cost;
                if (cap > 0 && dis[v] > d + h[u] - h[v] + cost) {
                    dis[v] = d + h[u] - h[v] + cost;
                    pre[v] = i;
                    que.emplace(dis[v], v);
                }
            }
        }
        return dis[t] != std::numeric_limits<T>::max();
    }
    MinCostFlow() {}
    MinCostFlow(int n_) {
        init(n_);
    }
    void init(int n_) {
        n = n_;
        e.clear();
        g.assign(n, {});
    }
    void addEdge(int u, int v, T cap, T cost) {
        g[u].push_back(e.size());
        e.emplace_back(v, cap, cost);
        g[v].push_back(e.size());
        e.emplace_back(u, 0, -cost);
    }
    std::pair<T, T> flow(int s, int t) {
        T flow = 0;
        T cost = 0;
        h.assign(n, 0);
        while (dijkstra(s, t)) {
            for (int i = 0; i < n; ++i) {
                h[i] += dis[i];
            }
            //if(h[t] > 0) returen make_pair(flow, cost);//可行流
            T aug = std::numeric_limits<int>::max();
            for (int i = t; i != s; i = e[pre[i] ^ 1].to) {
                aug = std::min(aug, e[pre[i]].cap);
            }
            for (int i = t; i != s; i = e[pre[i] ^ 1].to) {
                e[pre[i]].cap -= aug;
                e[pre[i] ^ 1].cap += aug;
            }
            flow += aug;
            cost += aug * h[t];
        }
        return std::make_pair(flow, cost);
    }
    struct Edge {
        int from;
        int to;
        T cap;
        T cost;
        T flow;
    };
    std::vector<Edge> edges() {
        std::vector<Edge> a;
        for (int i = 0; i < e.size(); i += 2) {
            Edge x;
            x.from = e[i + 1].to;
            x.to = e[i].to;
            x.cap = e[i].cap + e[i + 1].cap;
            x.cost = e[i].cost;
            x.flow = e[i + 1].cap;
            a.push_back(x);
        }
        return a;
    }
};

4.字符串

1.字符串哈希

核心思想:将字符串看成P进制数,P的经验值是13113331,取这两个值的冲突概率低
小技巧:取模的数用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];
}
struct Hash{
    #define maxn 200005
    ll hs1[maxn],hs2[maxn],pw1[maxn],pw2[maxn];
    inline void init(string s,const ll p1=998244353,const ll p2=19660813){
        hs1[0]=hs2[0]=0;pw1[0]=pw2[0]=1;
        for(ll i=1;i<s.size();++i){
            hs1[i]=(hs1[i-1]*131%p1+s[i]-'a'+1)%p1;
            hs2[i]=(hs2[i-1]*131%p2+s[i]-'a'+1)%p2;
            pw1[i]=pw1[i-1]*131%p1;
            pw2[i]=pw2[i-1]*131%p2;
        }
    }
    inline pair<ll,ll> qry(ll l,ll r,const ll p1=998244353,const ll p2=19660813){
        ll res1=(hs1[r]-hs1[l-1]*pw1[r-l+1]%p1+p1)%p1;
        ll res2=(hs2[r]-hs2[l-1]*pw2[r-l+1]%p2+p2)%p2;
        return make_pair(res1,res2);
    }
};
//hash h1, h2
//h1.init(s1), h2.init(s2) 下标从1开始
//h1.qry(l1, r2) == h2.qry(l2, r2)

2.KMP

vector<int> calcPi(string& p) {
    vector<int> pi(p.size());
    int match = 0;
    for (int i = 1; i < p.size(); i++) {
        char v = p[i];
        while (match > 0 && p[match] != v) {
            match = pi[match - 1];
        }
        if (p[match] == v) {
            match++;
        }
        pi[i] = match;
    }
    return pi;
}

// 在文本串 s 中查找模式串 p,返回所有成功匹配的位置(p[0] 在 s 中的下标)
vector<int> kmp_search(string& s, string p) {
    if (p.empty()) {
        // s 的所有位置都能匹配空串,包括 s.size()
        vector<int> pos(s.size() + 1);
        iota(pos.begin(), pos.end(), 0);
        return pos;
    }

    vector<int> pi = calcPi(p);
    vector<int> pos;
    int match = 0;
    for (int i = 0; i < s.size(); i++) {
        char v = s[i];
        while (match > 0 && p[match] != v) {
            match = pi[match - 1];
        }
        if (p[match] == v) {
            match++;
        }
        if (match == p.size()) {
            pos.push_back(i - p.size() + 1);
            match = pi[match - 1];
        }
    }
    return pos;
}

3.字典树(trie

字典树用于解决字符串的前缀及后缀问题,以及数的异或查找(可撤销的(01 trie )

int son[N][26], cnt[N*26], idx;

void insert(string s)
{
    int p = 0;
    for (int i = 0; i < s.size(); i ++ )
    {
        int u = s[i] - 'a';
        if (!son[p][u]) son[p][u] = ++ idx;
        p = son[p][u];
        cnt[p] ++ ;
    }

}
int query(string s)
{
    int p = 0, res = 0;
    for (int i = 0; i < s.size(); i ++ )
    {
        int u = s[i] - 'a';
        if (!son[p][u]) return 0;
        p = son[p][u];
        res += cnt[p];
    }
    return res;
}

4.Manacher

std::vector<int> manacher(std::string s) {
    std::string t = "#";
    for (auto c : s) {
        t += c;
        t += '#';
    }
    int n = t.size();
    std::vector<int> r(n);
    for (int i = 0, j = 0; i < n; i++) {
        if (2 * j - i >= 0 && j + r[j] > i) {
            r[i] = std::min(r[2 * j - i], j + r[j] - i);
        }
        while (i - r[i] >= 0 && i + r[i] < n && t[i - r[i]] == t[i + r[i]]) {
            r[i] += 1;
        }
        if (i + r[i] > j + r[j]) {
            j = i;
        }
    }
    return r;
}

5.杂项

1.s中t字符串的数量

阿宁的毒瘤题

//dp[ s.size() ] [ t.size() ]
//dp[0] [j] = 0; //初始化
//dp[i] [0]= 1; (包含空串)
//dp[0] [0]= 1 (防止第一个条件覆盖
int tnum(string s, string t)
{
    dp[0][0] = 1;
    for(int i = 1; i <= s.size(); i ++)
	{
    	dp[i][0] = 1;
    	for(int j = 1; j <= t.size(); j ++)
    	{
        	dp[i][j] = dp[i - 1][j];
        	if(s[i] == t[j]) dp[i][j] += dp[i - 1][j - 1];
    	}
	}
    return dp[s.size()][t.size()];
}

//一维数组
int tnumn(string s, string t)
{
    for(int i = 0; i < s.size(); i ++)
    {       
        for(int j = t.size() - 1; j >= 1; j --)
        {
            if(s[i] == str[j])
            {
                ans[j] += ans[j - 1];
            }
            
        }
        if(s[i] == str[0])   
        {
                ans[0] += 1;
        }
     } 
    return ans[2];
}

6.exKMP

z[i]代表s与s[i ~ n - 1]的lcp
求字符串的匹配数,找 p + "#" + t的z函数
考虑计算 s 的 Z 函数,则其整周期的长度为最小的 n 的因数 i,满足 i+z[i]=n
本质不同字串
vector<int> zfunction(string s) {
  int n = (int)s.size();
  vector<int> z(n);
  z[0] = n;
  for (int i = 1, l = 0, r = 0; i < n; ++i) {
    if (i <= r && z[i - l] < r - i + 1) {
      z[i] = z[i - l];//盒内包含前缀,对应点的z不超过盒外,可以直接转移
    } else {
      z[i] = max(0, r - i + 1);//对应点的z超过盒外,先取靠右
      while (i + z[i] < n && s[z[i]] == s[i + z[i]]) ++z[i];//后暴力更新
    }
    if (i + z[i] - 1 > r) l = i, r = i + z[i] - 1;//更新盒子边界
  }
  return z;
}

5. 数学

1. 快速幂

ll ksm(ll a,ll b)
{
    ll res = 1;
    while(b){
        if(b & 1) res = (ll)res*a % mod;
        a = (ll)a * a % mod;
        b >>= 1;
    }
    return res;
}
ll ksms(ll x, string p) {//字符串快速幂
    LL res = 1;
    x %= mod;
    for (int i = p.size() - 1; i >= 0; --i) {
        int digit = p[i] - '0';
        ll temp = 1;
        for (int j = 0; j < digit; ++j) {
            temp = (temp * x) % mod;
        }
        res = (res * temp) % mod;
        x = (x * x) % mod;
    }
    return res;
}

2.拓展欧几里得(exgcd)

原理 : gcd(a,b)=gcd(b,a % b) , x, y是 ax+by=c 的一个特解, 这里一定要 c %gcd(a,b)==0, 否则无解
而我们可以用扩展欧几里得求得特解,剩下得就是求齐次解了
齐次方程为 ax+by=0;
求得的解为 x=k(b/gcd(a,b));y=k(a/gcd(a,b));
所以 ax+by=c 的通解为
x=x0()+k(b/gcd(a,b));y=y0()k(a/gcd(a,b));

int exgcd(int a, int b, int &x, int &y)
{
    if (!b)
    {
        x = 1; y = 0;
        return a;
    }
    int d = exgcd(b, a % b, y, x);
    y -= (a/b) * x;
    return d;
}

3.求组合数的方法

struct Comb {
    const int N;
    vector<ll> fac, invfac;
    Comb(int n) : N(n), fac(n + 2), invfac(n + 2) { init(); };
    ll qpow(ll x, ll p) {
        ll res = 1 % mod; x %= mod;
        for (; p; p >>= 1, x = x * x % mod)
            if (p & 1) res = res * x % mod;
        return res;
    }
    ll inv(ll x) { return qpow(x, mod - 2); };
    void init() {
        fac[0] = 1;
        for (int i = 1; i <= N; ++i) fac[i] = fac[i - 1] * i % mod;
        invfac[N] = inv(fac[N]);
        for (int i = N - 1; i >= 0; --i) invfac[i] = (invfac[i + 1] * (i + 1)) % mod;
    }
    ll C(int n, int m) {
        if (n < m || m < 0) return 0;
        return fac[n] * invfac[m] % mod * invfac[n - m] % mod;
    }
    ll A(int n, int m) {
        if (n < m || m < 0) return 0;
        return fac[n] * invfac[n - m] % mod;
    }
};

4. 矩阵快速幂求斐波那契数列

n 很大时,要用矩阵快速幂求解,时间复杂度是 logN

#include <iostream>
#include <cstring>

#define Max_rank 3
#define mod 1000000007
struct Matrix {
    long long a[Max_rank][Max_rank];

    Matrix() {
        memset(a, 0, sizeof(a));
    }

    void init(){
        a[1][1] = a[1][2] = a[2][1] = 1;
        a[2][2] = 0;
    }

    Matrix operator*(const Matrix b) {
        Matrix res;
        for (int i = 1; i <= 2; i++)
            for (int j = 1; j <= 2; j++)
                for (int u = 1; u <= 2; u++)
                    res.a[i][j] = (res.a[i][j] + a[i][u]*b.a[u][j])%mod;
        return res;
    }
};

long long q_pow(long long n){
    Matrix ans,base;
    ans.init();
    base.init();
    //这里要填矩阵值
    while(n > 0){
        if(n&1) ans =ans *base;
        base = base *base;
        n >>= 1;
    }
    return ans.a[1][1];
}
int main() {
    long long n;
    while(std::cin >> n){
        std::cout << q_pow(n-2) << std::endl;//迭代次数自定义,一般为n - 1次
    }
    return 0;
}

5.欧拉函数

//1 - n 中与n互质的个数
int phi(int x)
{
    int res = x;
    for (int i = 2; i <= x / i; i ++ )
        if (x % i == 0)
        {
            res = res / i * (i - 1);
            while (x % i == 0) x /= i;
        }
    if (x > 1) res = res / x * (x - 1);

    return res;
}

6. 素数测试与因式分解(Miller-Rabin & Pollard-Rho)

i64 mul(i64 a, i64 b, i64 m) {
    return static_cast<__int128>(a) * b % m;
}
i64 power(i64 a, i64 b, i64 m) {
    i64 res = 1 % m;	
    for (; b; b >>= 1, a = mul(a, a, m))
        if (b & 1)
            res = mul(res, a, m);
    return res;
}
bool isprime(i64 n) {
    if (n < 2)
        return false;
    static constexpr int A[] = {2, 3, 5, 7, 11, 13, 17, 19, 23};
    int s = __builtin_ctzll(n - 1);
    i64 d = (n - 1) >> s;
    for (auto a : A) {
        if (a == n)
            return true;
        i64 x = power(a, d, n);
        if (x == 1 || x == n - 1)
            continue;
        bool ok = false;
        for (int i = 0; i < s - 1; ++i) {
            x = mul(x, x, n);
            if (x == n - 1) {
                ok = true;
                break;
            }
        }
        if (!ok)
            return false;
    }
    return true;
}
std::vector<i64> factorize(i64 n) {
    std::vector<i64> p;
    std::function<void(i64)> f = [&](i64 n) {
        if (n <= 10000) {
            for (int i = 2; i * i <= n; ++i)
                for (; n % i == 0; n /= i)
                    p.push_back(i);
            if (n > 1)
                p.push_back(n);
            return;
        }
        if (isprime(n)) {
            p.push_back(n);
            return;
        }
        auto g = [&](i64 x) {
            return (mul(x, x, n) + 1) % n;
        };
        i64 x0 = 2;
        while (true) {
            i64 x = x0;
            i64 y = x0;
            i64 d = 1;
            i64 power = 1, lam = 0;
            i64 v = 1;
            while (d == 1) {
                y = g(y);
                ++lam;
                v = mul(v, std::abs(x - y), n);
                if (lam % 127 == 0) {
                    d = std::gcd(v, n);
                    v = 1;
                }
                if (power == lam) {
                    x = y;
                    power *= 2;
                    lam = 0;
                    d = std::gcd(v, n);
                    v = 1;
                }
            }
            if (d != n) {
                f(d);
                f(n / d);
                return;
            }
            ++x0;
        }
    };
    f(n);
    std::sort(p.begin(), p.end());
    return p;
}

int idx;//质因数组合
int p[100010], cnt[100010];//存质因子
vector<int> getDivisor()
{
    vector<int> divisor(100010, 0);
    divisor[0] = 1;
    divisor[1] = 1;
    for (int i = 1; i <= p[0]; ++i)
    {
        int nowNum = divisor[0];
        int base = 1;
        for (int j = 1; j <= cnt[i]; ++j)
        {
            base *= p[i];
            for (int k = 1; k <= divisor[0]; ++k)
                divisor[++nowNum] = divisor[k] * base;
        }
        divisor[0] = nowNum;
    }
    return divisor;
}

7.高斯消元

1.异或方程组(abc366_g)

#include<bits/stdc++.h>

using namespace std;
using ull = unsigned long long;
using ll = long long;
using PII = pair<int,int>;
#define IOS ios::sync_with_stdio(false),cin.tie(0)
#define lowbit(x) (x) & (-x)
#define endl "\n"
#define pb push_back
#define point(x) setiosflags(ios::fixed)<<setprecision(x)
const int N=1e2+10;
const int INF=0x3f3f3f3f;
const int mod=998244353;

bitset<N> g[N];
ll res[N];
void solve()
{
	int n, m; cin >> n >> m;
	for(int i = 1; i <= m; i ++) {
		int u, v; cin >> u >> v;
		g[u][v] = g[v][u] = 1;
	}
	ll w = 1;
	for(int i = 1; i <= n; i ++) {
        int pos = i;
        while(pos <= n && g[pos][i] == 0) pos ++;
        if(pos > n) {//多解自由元
            res[i] = w;
            w <<= 1;
            continue;
        } 
        if(pos != i) swap(g[i], g[pos]);			
		for(int j = 1; j <= n; j ++) {
			if(g[j][i] && i != j) {
				g[j] ^= g[i];				
			}
		}
	}
	for(int i = 1; i <= n; i ++) {
		if(res[i]) continue;
		for(int j = 1; j <= n; j ++) {
			if(g[i][j] && i != j) {//b列值为0这样操作
				res[i] ^= res[j];
			}
		}
		if(res[i] == 0) {
			cout << "No" << endl;
			return;
		}
	}
	cout << "Yes" << endl;
	for(int i = 1; i <= n; i ++) {
		cout << res[i] << ' ';
	}
}

2.普通方程组

const double EPS = 1E-9;
int n;
vector<vector<double> > a(n, vector<double>(n));

double det = 1;
for (int i = 0; i < n; ++i) {
  int k = i;
  for (int j = i + 1; j < n; ++j)
    if (abs(a[j][i]) > abs(a[k][i])) k = j;
  if (abs(a[k][i]) < EPS) {
    det = 0;
    break;
  }
  swap(a[i], a[k]);
  if (i != k) det = -det;
  det *= a[i][i];
  for (int j = i + 1; j < n; ++j) a[i][j] /= a[i][i];
  for (int j = 0; j < n; ++j)
    if (j != i && abs(a[j][i]) > EPS)
      for (int k = i + 1; k < n; ++k) a[j][k] -= a[i][k] * a[j][i];
}

cout << det;

8.欧拉降幂

abmodc=abmodφ(c)+φ(c)modc

φ(c) 为欧拉函数

 
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
const int MAX=1000100;
ll  fastPow(ll  a,ll b,ll mod)
{
    ll ans=1;
    a %= mod;
    while(b)
    {
        if(b&1)
        {
            ans = (ans*a)%mod;
        }
        b >>= 1;
        a = (a*a)%mod;
    }
    return ans;
}
ll  eulerFunction(ll x)
{
    ll eulerNumbers = x;
    for(ll i = 2; i*i <= x; i++)
    {
        if(x % i == 0)
        {
            eulerNumbers = eulerNumbers / i * (i-1);
            while(x % i == 0)
            {
                x /= i;
            }
        }
    }
    if(x > 1)
    {
        eulerNumbers = eulerNumbers / x * (x-1);
    }
    return eulerNumbers;
}
ll eulerDropPow(ll a,char b[],ll c)
{
    ll eulerNumbers = eulerFunction(c);
    ll descendingPower=0;
    for(ll i=0,len = strlen(b); i<len; ++i)
    {
        descendingPower=(descendingPower*10+b[i]-'0') % eulerNumbers;
    }
    descendingPower += eulerNumbers;
    return fastPow(a,descendingPower,c);
}
int main()
{
    ll a,c;
    char b[MAX];
    while(~scanf("%lld%s%lld",&a,b,&c))  
    {
        printf("%lld\n",eulerDropPow(a,b,c));
    }
    return 0;
}

9.线性基

集合的最大异或和(所有的p异或), 最小异或和(最小的p), 第k大(小)异或和(p有t个元素,第k大就是2^t - k的二进制对应的行)
查询某个数是否能被异或出来,类似于插入,如果最后插入的数 p 被异或成了 0,则能被异或出来。
ll p[70];
bool zero;
void insert(ll x) {
    for(int i = 63; i >= 0; i --) {
        if(x >> i & 1) {
            if(p[i] == 0) {
                p[i] = x;
                return ;
            } else {
                x ^= p[i];
            }
        }
    }
    zero = true;
}
ll qmax(int k) {//第一大
    int cnt = 0;
    for(int i = 63; i >= 0; i --) cnt += (p[i] > 0);
    ll num = pow(2, cnt);
    k = num - k;
    ll res = 0;
    for(int i = 63; i >= 0; i --) {
        if(k >> 1 & 1)
        	res = max(res, res ^ p[i]);
    }
    return res;
}

6.计算几何

1.求凸包

using i64 = double;
struct Point {
    i64 x;
    i64 y;
    Point(i64 x = 0, i64 y = 0) : x(x), y(y) {}
};

bool operator==(const Point &a, const Point &b) {
    return a.x == b.x && a.y == b.y;
}

Point operator+(const Point &a, const Point &b) {
    return Point(a.x + b.x, a.y + b.y);
}

Point operator-(const Point &a, const Point &b) {
    return Point(a.x - b.x, a.y - b.y);
}

i64 dot(const Point &a, const Point &b) {//点积
    return a.x * b.x + a.y * b.y;
}

i64 cross(const Point &a, const Point &b) {//叉积
    return a.x * b.y - a.y * b.x;
}
i64 dis(const Point &a, const Point &b) {
    //return sqrt((a.x -b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y)); 
    return sqrt((a - b).x * (a - b).x + (a - b).y * (a - b).y);
}
void norm(std::vector<Point> &h) {
    int i = 0;
    for (int j = 0; j < int(h.size()); j++) {
        if (h[j].y < h[i].y || (h[j].y == h[i].y && h[j].x < h[i].x)) {
            i = j;
        }
    }
    std::rotate(h.begin(), h.begin() + i, h.end());
}

int sgn(const Point &a) {
    return a.y > 0 || (a.y == 0 && a.x > 0) ? 0 : 1;
}

std::vector<Point> getHull(std::vector<Point> p) {
    std::vector<Point> h, l;
    std::sort(p.begin(), p.end(), [&](auto a, auto b) {
        if (a.x != b.x) {
            return a.x < b.x;
        } else {
            return a.y < b.y;
        }
    });
    p.erase(std::unique(p.begin(), p.end()), p.end());
    if (p.size() <= 1) {
        return p;
    }
    
    for (auto a : p) {
        while (h.size() > 1 && cross(a - h.back(), a - h[h.size() - 2]) <= 0) {
            h.pop_back();
        }
        while (l.size() > 1 && cross(a - l.back(), a - l[l.size() - 2]) >= 0) {
            l.pop_back();
        }
        l.push_back(a);
        h.push_back(a);
    }
    l.pop_back();
    std::reverse(h.begin(), h.end());
    h.pop_back();
    l.insert(l.end(), h.begin(), h.end());
    return l;
}

2.旋转卡壳

用来求凸包最大距离点对(多校),最短距离点对

int n;

const double eps=1e-9;
int dcmp(double x){
    return (fabs(x)<=eps)?0:(x<0?-1:1);
}
struct Point{
    double x,y;
    Point(double X=0,double Y=0){x=X,y=Y;}
};
struct Vector{
    double x,y;
    Vector(double X=0,double Y=0){x=X,y=Y;}
};
inline Vector operator-(Point x,Point y){// 点-点=向量
    return Vector(x.x-y.x,x.y-y.y);
}
inline double cross(Vector x,Vector y){ // 向量叉积
    return x.x*y.y-x.y*y.x;
}
inline double operator*(Vector x,Vector y){ // 向量叉积
    return cross(x,y);
}
inline double len(Vector x){ // 向量模长
    return sqrt(x.x*x.x+x.y*x.y);
}

int stk[50005];
bool used[50005];
vector<Point> ConvexHull(Point* poly, int n){ // Andrew算法求凸包
    int top=0;
    sort(poly+1,poly+n+1,[&](Point x,Point y){
        return (x.x==y.x)?(x.y<y.y):(x.x<y.x);
    });
    stk[++top]=1;
    for(int i=2;i<=n;i++){
        while(top>1&&dcmp((poly[stk[top]]-poly[stk[top-1]])*(poly[i]-poly[stk[top]]))<=0){
            used[stk[top--]]=0;
        }
        used[i]=1;
        stk[++top]=i;
    }
    int tmp=top;
    for(int i=n-1;i;i--){
        if(used[i]) continue;
        while(top>tmp&&dcmp((poly[stk[top]]-poly[stk[top-1]])*(poly[i]-poly[stk[top]]))<=0){
            used[stk[top--]]=0;
        }
        used[i]=1;
        stk[++top]=i;
    }
    vector<Point> a;
    for(int i=1;i<=top;i++){
        a.push_back(poly[stk[i]]);
    }
    return a;
}

struct Line{
    Point x;Vector y;
    Line(Point X,Vector Y){x=X,y=Y;}
    Line(Point X,Point Y){x=X,y=Y-X;}
};

inline double DistanceToLine(Point P,Line x){// 点到直线的距离
    Vector v1=x.y, v2=P-x.x;
    return fabs(cross(v1,v2))/len(v1);
}

double RoatingCalipers(vector<Point> poly){// 旋转卡壳
    if(poly.size()==3) return len(poly[1]-poly[0]);
    int cur=0;
    double ans=0;
    for(int i=0;i<poly.size()-1;i++){
        Line line(poly[i],poly[i+1]);
        while(DistanceToLine(poly[cur], line) <= DistanceToLine(poly[(cur+1)%poly.size()], line)){
            cur=(cur+1)%poly.size();
        }
        ans=max(ans, max(len(poly[i]-poly[cur]), len(poly[i+1]-poly[cur])));
    }
    return ans;
}

Point poly[50005];

signed main(){
	//used,stk清空,1下标开始的数组
    cin>>n;
    for(int i=1;i<=n;i++) cin>>poly[i].x>>poly[i].y;
    double v=RoatingCalipers(ConvexHull(poly, n));
    cout<<(int)(v*v);
    return 0;
}

7.动态规划

1.数位dp

适用于大范围统计满足某个约束的数量,memo记忆化维度取决于除了后面的约束外的个数,例如数位之和可以加个sum

将 n 转换成字符串 s,定义 f(i,lim,num) 表示构造从左往右第 i 位及其之后数位的合法方案数,其中:
lim 表示当前是否受到了 n 的约束。若为真,则第 i 位填入的数字至多为 s[i],否则至多为 9。例如 n=234,如果前面填了 23,那么最后一位至多填 4;如果前面填的不是 23,那么最后一位至多填 9。如果在受到约束的情况下填了 s[i],那么后续填入的数字仍会受到 n 的约束

num 表示 i 前面的数位是否填了数字。若为假,则当前位可以跳过(不填数字),或者要填入的数字至少为 1;若为真,则必须填数字,且要填入的数字从 0 开始。这样我们可以控制构造出的是一位数/两位数/三位数等等。

对于一个固定的 i,它受到 limnum 的约束在整个递归过程中至多会出现一次,没必要记忆化。比如 n=234,当 i=2 的时候,前面可以填 11,12,13,,23,如果受到 lim 的约束,就说明前面填的是 23。「当 i=2 的时候,前面填的是 23」这件事情,在整个递归过程中至多会出现一次。
另外,如果只记忆化 idp 数组的含义就变成在不受到 n 的约束时的合法方案数,所以要在 !lim && num 成立时才去记忆化。接着上面的例子,在前面填 23 的时候,下一位填的数字不能超过 4,因此算出来的结果是不能套用到前面填的是 11,12,13, 这些数字上面的。多维也一样

void solve {
    int m = to_string(n).size();
    int memo[m];
    memset(m, -1, sizeof memo);
    auto dfs = [&](auto &&dfs, int i, bool lim, bool num) -> int {
        if(i == m) return num;
        if(!lim && num && memo[i] != -1) return memo[i];
        int res = 0;
        if(!num) res += dfs(dfs, i + 1, false, false);
        char up = lim ? s[i] : '9';
        for(int d = 1 - num; d <= up; d ++) {
            res += dfs(dfs, i + 1, lim && d == up, true);
        }
        if(!lim && num) memo[i] = res;
        return res;
    
    };
    dfs(dfs, 0, true, false);
}


8.杂项

1. __int128的输入输出

__int128 read(){
    __int128 x=0;bool f=0;char c=getchar();
    while (c<'0'||c>'9'){if (c=='-')f=1;c=getchar();}
    while (c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return f?-x:x;
}
inline void write(__int128 x)
{
     if(x<0) putchar('-'),x=-x;
     if(x>9) write(x/10);
     putchar(x%10+'0');
}

2.如何找到p/q的循环节?

考虑1/q
1.如果q中仅含有2或5,那么该小数是有限循环小数
2.如果不含2或5,10与q互素,根据欧拉定理,10^Φ(q)≡1(%q),其中Φ(q)便是循环节,但不一定是最小的,因为最小的循环节重复k遍仍是循环节,所以通过遍历Φ(q)的因子找到最小的循环节即可。
3.如果出了2和5还有其他的,先找出2和5的个数分别为a与b,循环节前面一段的长度就是max(a,b),剩下的10与q除掉2a,5b互素,找到最小循环节即可。

#include<bits/stdc++.h>
//大整数取模要用__int128
using namespace std;
using ull = unsigned long long;
using ll = long long;
using PII = pair<int,int>;
#define endl "\n"
#define pb push_back
#define IOS ios::sync_with_stdio(false);cin.tie(0)
const int N=1e5+10;
const int INF=0x3f3f3f3f;
__int128 gcd(__int128 a, __int128 b) { return b ? gcd(b, a % b) : a;}
__int128 read(){
    __int128 x=0;bool f=0;char c=getchar();
    while (c<'0'||c>'9'){if (c=='-')f=1;c=getchar();}
    while (c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return f?-x:x;
}
inline void write(__int128 x)
{
     if(x<0) putchar('-'),x=-x;
     if(x>9) write(x/10);
     putchar(x%10+'0');
}
__int128 phi(__int128 x)
{
    __int128 res = x;
    for(__int128 i = 2; i <= x / i; i ++)
        if(x % i == 0)
        {
            res = res / i * (i - 1);
            while(x % i == 0) x /= i;
        }
    if(x > 1) res = res / x * (x - 1);
    return res;
}
__int128 ksm(__int128 a,__int128 b,__int128 mod)
{
    __int128 res = 1;
    while(b){
        if(b & 1) res = (__int128)res*a % mod;
        a = (__int128)a * a % mod;
        b >>= 1;
    }
    return res;
}
int main()
{
	__int128 p, q;
    p = read(), q = read();
    __int128 d = gcd(p, q);
    q /= d;
    __int128 cn2 = 0, cn5 = 0;
    while(q % 2 == 0) q /= 2, cn2 ++;
    while(q % 5 == 0) q /= 5, cn5 ++;
    //write(q);
    //cout << endl;
    if(q == 1)
    {
        cout << "-1" << endl;
        return 0;
    }
    __int128 t = phi(q);
    __int128 res = 1e18;
    for(__int128 i = 1; i <= t / i; i ++)
    {
        if(t % i == 0)
        {
            __int128 x1 = i;
            __int128 x2 = t / i;
            if(ksm(10, x1, q) == 1) res = min(res, x1);
            if(ksm(10, x2, q) == 1) res = min(res, x2);
        }
    }
    write(max(cn2, cn5));
    cout << ' ';
    write(res);
	return 0; 
}

3.pbds库

平衡树操作

insert(x):向树中插入一个元素 x,返回 std::pair<point_iterator, bool>。
erase(x):从树中删除一个元素/迭代器 x,返回一个 bool 表明是否删除成功。
order_of_key(x):返回 x 以 Cmp_Fn 比较的排名。
find_by_order(x):返回 Cmp_Fn 比较的排名所对应元素的迭代器。
lower_bound(x):以 Cmp_Fn 比较做 lower_bound,返回迭代器。
upper_bound(x):以 Cmp_Fn 比较做 upper_bound,返回迭代器。
join(x):将 x 树并入当前树,前提是两棵树的类型一样,x 树被删除。
split(x,b):以 Cmp_Fn 比较,小于等于 x 的属于当前树,其余的属于 b 树。
empty():返回是否为空。
size():返回大小。

文件定义

#include<bits/extc++.h>
using namespace __gnu_pbds;


#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>   // 用tree

using namespace std;
using namespace __gnu_pbds;

using TR = tree<ll, null_type, less<ll>, rb_tree_tag, tree_order_statistics_node_update>;

4. 对拍

//需要的文件
data.in
solve.out
std.out
diff.log
duipai.cpp
solve.cpp//现在代码
std.cpp//暴力正确代码
data.cpp//造数据

#include<bits/stdc++.h>

using namespace std;

int main() {
	int t = 0;
	while(1) {
		cout << "test : " << t ++ << endl;
		system("data.exe > data.in");
		system("std.exe < data.in > std.out");
		system("solve.exe < data.in > solve.out");
		if(system("fc std.out solve.out > diff.log")) {
			cout << "WA\n";
			break; 
		}
		cout << "AC\n";
	}
}
//#define random(a,b) ((a)+rand()%((b)-(a)+1))
#define random(a,b) ((a)+sj()%((b)-(a)+1))
typedef long long ll;
typedef unsigned long long ull;
int dsu[1000005];//1e6
int mapp[10000005];
int a[1000010];
mt19937 sj(time(0));
void create(){//随机树构造
    int n=random(1,10);
    for(int i=n;i>=2;--i){
        dsu[i]=random(1,i-1);
    }
    for(int i=1;i<=n;++i) mapp[i]=i;
    random_shuffle(mapp+1,mapp+n+1);
    printf("%d\n",n);
    for(int i=2;i<=n;++i){
        printf("%d %d\n",mapp[i],mapp[ dsu[i] ]);
    }
    return;
}
#include<bits/stdc++.h>//随机数

using namespace std;
//#define random(a,b) ((a)+rand()%((b)-(a)+1))
#define random(a,b) ((a)+sj()%((b)-(a)+1))
typedef long long ll;
typedef unsigned long long ull;
int dsu[1000005];//1e6
int mapp[10000005];
int a[1000010];
mt19937 sj(time(0));

int main(){

    return 0;
}

本文作者:zouyua

本文链接:https://www.cnblogs.com/ZouYua/p/18742391

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   zouyua  阅读(8)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起