各种OI板子

发布时间 2023-10-12 17:33:13作者: carp_oier

以下内容不定时更新,想到啥写啥。。

读写优化

快读

code
template <class T> inline void read(T &res)
{
	char ch = getchar(); bool f = 0; res = 0;
	for(; !isdigit(ch); ch = getchar()) f |= ch == '-';
	for(; isdigit(ch); ch = getchar()) res = (res << 1) + (res << 3) + (ch  ^ 48);
	res = f ? ~res + 1 : res;
}


快写

code
template <class T> inline void waw(T x)
{
	if(x > 9) waw(x / 10);
	putchar(x % 10 ^ 48);
}

template <class T> inline void ww(T x)
{
	if(x < 0) x = ~x + 1, putchar('-');
	waw(x);
}


基础算法

排序

快排

直接用自带的函数实现即可,sort(起点,终点,排列规则(这个可以没有))。

归并

通常会用来求逆序对。

code
void merge_sort(int q[], int l, int r)
{
    if (l >= r) return;

    int mid = l + r >> 1;
    merge_sort(q, l, mid);
    merge_sort(q, mid + 1, r);

    int k = 0, i = l, j = mid + 1;
    while (i <= mid && j <= r)
        if (q[i] <= q[j]) tmp[k ++ ] = q[i ++ ];
        else tmp[k ++ ] = q[j ++ ];

    while (i <= mid) tmp[k ++ ] = q[i ++ ];
    while (j <= r) tmp[k ++ ] = q[j ++ ];

    for (i = l, j = 0; i <= r; i ++, j ++ ) q[i] = tmp[j];
}

\(O(n)\) 的基数排序(位运算版本)

最快的排序方法。

code ``` inline void Sort() { static ll p = 65535; for (rl i = 1; i <= n; ++i) c[a[i] & p]++; for (rl i = 1; i <= p; ++i) c[i] += c[i - 1]; for (rl i = n; i >= 1; --i) b[c[a[i] & p]--] = i; memset(c, 0, sizeof c); for (rl i = 1; i <= n; ++i) c[(a[b[i]] >> 16) & p]++; for (rl i = 1; i <= p; ++i) c[i] += c[i - 1]; for (rl i = n; i >= 1; --i) d[c[(a[b[i]] >> 16) & p]--] = b[i]; for (rl i = 1; i <= n; ++i) b[i] = d[i]; } ```

排完序后 b 中是排好的序列。


图论

最短路

Floyd

code
for(rl k=1; k <= n; ++ k)
	for(rl i=1; i <= n; ++ i)
		for(rl j=1; j <= n; ++ j)
			g[i][j] = min(g[i][j], g[i][k] + g[k][j]);
			
// 传递闭包

#define for(i, a, b) for(rl i=a; i <= b; ++ i)

for(k, 1, n)
	for(i, 1, n)
		for(j, 1, n)
			g[i][j] |= g[i][k] & g[k][j];

spfa

code
inline bool spfa()
{
	queue<ll> q;
	memset(dis, 0x3f, sizeof dis);
	dis[s] = 0, q.push(s);
	
	while(!q.empty())
	{
		ll u = q.front();
		q.pop(); st[u] = false;
		for(rl i=h[u]; ~i; i = ne[i])
		{
			ll v = e[i];
			if(dis[v] > dis[u] + w[i])
			{
				dis[v] = dis[u] + w[i];
				sla[v] = sla[u] + 1;
				if(sla[v] >= n + 2) return true; // 判断是否存在负环
				if(!st[v]) st[v] = true, q.push(v);
			}
		}
	}
	
	return true;
}

dijkstra( + heap)

code
struct node
{
	ll id, dis;
	bool operator <(const node &x) const 
	{
		return dis > x.dis;
	}
};

inline void dijkstra()
{
	priority_queue<node> q;
	memset(dis, 0x3f, sizeof dis);
	dis[s] = 0, q.push({s, 0});
	
	while(q.size())
	{
		auto t = q.top();
		q.pop();
		ll u = t.id;
		if(st[u]) continue;
		st[u] = true;
		for(rl i=h[u]; ~i; i = ne[i])
		{
			ll v = e[i];
			if(dis[v] > dis[u] + w[i])
			{
				dis[v] = dis[u] + w[i];
				q.push({v, dis[v]});
			}
		}
	}
}

最小生成树

kruskal

code
struct node
{
	ll u, v, w;
	bool used;
}e[M];

bool cmp(node a, node b) { return a.w < b.w; }

ll find(ll x) { return p[x] == x ? x : p[x] = find(p[x]); }

int main()
{
	scanf("%lld %lld", &n, &m);
	for(i ,1, n) p[i] = i;
	for(i, 1, m) scanf("%lld %lld %lld ", &e[i].u, &e[i].v, &e[i].w);
	sort(e + 1, e + m + 1, cmp);
	
	for(i, 1, m)
	{
		ll a = find(e[i].u), b = find(e[i].v), w = e[i].w;
		if(a != b)
		{
			ans += w;
			p[a] = b;
			e[i].used = true;
			if( ++ cnt = n - 1) break;
		}
	}
	return 0;
}

强连通分量相关

单连通分量

ll dfn[N], low[N], timestamp, dep[N], scc_cnt, scc_size[N];

ll top, stk[N], id[N];

bool in_stk[N];

vector<ll> scc[N];

inline void tarjan(ll u)
{
	in_stk[u] = true;
	dfn[u] = low[u] = ++ timestamp, stk[ ++ top] = u;
	
	for(rl i=h[u]; ~i; i = ne[i])
	{
		ll v = e[i];
		if(!dfn[v]) { tarjan(v); low[u] = min(low[u], low[v]); }
		else if(in_stk[v]) low[u] = min(low[u], low[v]);
	}
	
	if(low[u] == dfn[u])
	{
		ll ele; scc_cnt ++ ;
		do	{
			ele = stk[top -- ], in_stk[ele] = false;
			scc_size[scc_cnt] ++, scc[scc_cnt].push_back(ele);
			id[ele] = scc_cnt;
		} while(ele != u);
	}
}
点双连通分量
ll dfn[N], low[N], dcc_cnt, dcc_size[N];

ll stk[N], top, id[N];

bool cut[N];

vector<ll> dcc[N];

inline void tarjan(ll u)
{
	dfn[u] = low[u] = ++ timestamp, stk[ ++ top] = u;
	
	if(u == root && h[u] == -1)
	{
		dcc_cnt ++, dcc[dcc_cnt].push_back(u);
		return ;
	}
	
	ll cnt = 0;
	
	for(rl i=h[u]; ~i; i = ne[i])
	{
		ll v = e[i];
		if(!dfn[v])
		{
			tarjan(v), low[u] = min(low[u], low[v]);
			if(dfn[u] <= low[v])
			{
				 ++ cnt; 
				 if(u != root || cnt > 1) cut[u] = true;
				 ++ dcc_cnt; 
				 ll ele;
				 do {
				 	ele = stk[ top -- ], dcc[dcc_cnt].push_back(ele);
				 } while(u != v);
				 dcc[dcc_cnt].push_back(u);
			}
		}
		else low[u] = min(low[u], dfn[v]);
	}
}
边双连通分量
ll dfn[N], low[N], timestamp, id[N], dcc_cnt, dcc_size[N];

ll stk[N], top;

inline void tarjan(ll u, ll from)
{
	dfn[u] = low[u] = ++ timestamp;
	stk[ ++ top] = u; in_stk[u] = true;
	
	for(rl i=h[u]; ~i; i = ne[i])
	{
		ll v = e[i];
		if(!dfn[v]) 
		{
			tarjan(v), low[u] = min(low[u], low[v]);
			if(dfn[u] < low[v]) is_bridge[i] = is_bridge[i ^ 1] = true;
		}
		else if(i != (from ^ 1)) low[u] = min(low[u], dfn[v]);
	}
	
	if(dfn[u] == low[u])
	{
		ll ele; 
		dcc_cnt ++ ;
		do {
			ele = stk[top -- ]; dcc_size[dcc_cnt] ++ ;
			dcc[dcc_cnt].push_back(ele); id[ele] = dcc_cnt;
		} whie(ele != u);
	}

匈牙利算法

\(最小点覆盖 \leftrightarrow 最大匹配数 \leftrightarrow 顶点数 - 最小边覆盖 \leftrightarrow 顶点数 - 最大独立集\)

一维
inline bool find(ll u)
{
	for(rl i=h[u]; ~i; i = ne[i])
	{
		ll v = e[i], t = match[v];
		if(st[v]) continue;
		st[v] = true;
		if(!t || find(r)) {match[v] = u; return true; }
	}
	return false;
}

int main()
{
	ll res = 0;
	
	for(i, 1, n)
	{
		memset(st, 0, sizeof st);
		if(find(i)) res ++ ;
	}
	return 0;
}
二维
typedef pair<ll, ll> pll;

pll match[N][N];

ll dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0}; // 不同的方向数组可能不同

inline bool find(ll x, ll y)
{
	for(i, 0, 3)
	{
		ll a = x + dx[i], b = y + dy[i];
		if(a < 1 || a > n || b < 1 || b > m) continue;
		if(st[a][b] || g[a][b]) continue; // st 为是否查询过,g 为是否能够放置棋子
		st[a][b] = true;
		
		auto = match[a][b];
		
		if(!t.x || find(t.x, t.y)) { match[a][b] = {x, y}; return true; }
	}
	
	return false;
}

差分约束

将式子转化为

\[x_a - x'_a \le k \]

连接从 \(x'_a\)\(x_a\) 一条长度为 \(k\) 的边

倍增法求LCA

code
#define ll long long 
#define rl register ll 

ll fa[N][M], dep[N];

inline void bfs()
{
    memset(dep, 0x3f, sizeof dep);

    dep[0] = 0, dep[1] = 1;

    ll hh = 0, tt = -1;

    q[ ++ tt] = 1;

    while(hh <= tt)
    {
        ll u = q[hh ++ ];
        for(rl i=h[u]; ~i; i = ne[i]) // 我的邻接表初始化为-1
        {
            ll v = e[i];
            if(dep[v] > dep[u] + 1)
            {
                dep[v] = dep[u] + 1;
                q[ ++ tt] = v;
                fa[v][0] = u;
                for(rl k=1; k < 21; ++ k)
                    fa[v][k] = fa[fa[v][k-1]][k-1];
            }
        }
    }
}

inline ll lca(ll a,  ll b)
{
    if(dep[a] < dep[b]) swap(a, b);
    for(rl i = 21; i >= 0; -- i)
        if(dep[fa[a][i]] >= dep[b])
            a = fa[a][i];

    if(a == b) return a;

    for(rl i=21; i >= 0; -- i)
        if(fa[a][i] != fa[b][i])
            a = fa[a][i], b = fa[b][i];
        
    return fa[a][0];
}

O(1) 复杂度访问LCA

code
ll dep[N], st[M][N], dfn[N], idx;

inline ll get(ll a, ll b) { return dep[a] > dep[b] ? b : a; }

inline void dfs(ll u, ll fa)
{
    dep[u] = dep[fa] + 1, dfn[u] = ++ idx, st[0][idx] = fa;
    for(rl i=h[u]; ~i; i = ne[i]) { ll v = e[i]; if(v == fa) continue; dfs(v, u); } // 原因同上
}

inline void init()
{
    dfs(root, -1);

    for(rl j=0; j <= M; ++ j)
        for(rl i=1; i + (1 << j) - 1 <= n; ++ i)
            st[j][i] = get(st[j - 1][i], st[j - 1][i + (1 << j - 1)]);
}

inline ll lca(ll a, ll b)
{
    if(a == b) return a;
    if((a = dfn[a]) > (b = dfn[b])) swap(a, b);
    ll l = __lg(b - a);
    return  get(st[l][a + 1], st[l][b - (1 << l) + 1]);
}

数据结构

树状数组(一维,单点修改,区间查询)

如若想要区间修改,单点查询,则只需要对存储的序列进行差分。然后存入树状数组

code
#define lowbit(x) (x & -x)

inline void add(ll x, ll c)
{
	for(; x <= n; x += lowbit(x)) tr[x] += c;
}

inline ll sum(ll x)
{
	ll res = 0;
	for(; x; x -= lowbit(x)) res += tr[x];
	return res;
}

树状数组(二维,区间修改,区间查询)

树状数组在存储的时候同样也是存储差分序列。2023-10-12 16:20:18 星期四

code
#define lowbit(x) (x & -x)

ll tr1[N], tr2[N];

inline void add(ll x, ll c)
{
	ll i = x - 1;
	for(; x <= n; x += lowbit(x)) tr1[x] += c, tr2[x] += i * c;
}

inline void sum(ll x)
{
	ll res = 0, resu = 0, i = x;
	for(; x; x -= lowbit(x)) res += tr1[x], resu += tr2[x];
	return i * res - resu
}


树链剖分

注意下面有打注释的地方不同题目可能不同,只是放了一些常见的操作。

code

inline void add(ll a, ll b) { e[ ++ tot] = {b, h[a]}, h[a] = tot; }

inline void dfs1(ll u, ll p, ll d)
{
    fa[u] = p, dep[u] = d, sz[u] = 1;
    for(rl i=h[u]; ~i; i = e[i].ne)
    {
        ll v = e[i].v;
        if(v == p) continue;
        dfs1(v, u, d + 1);
        sz[u] += sz[v];
        if(sz[son[u]] < sz[v]) son[u] = v;
    }
}

inline void dfs2(ll u, ll t)
{
    top[u] = t, id[u] = ++ cnt, nw[cnt] = w[u];

    if(!son[u]) return ;
    
    dfs2(son[u], t);

    for(rl i=h[u]; ~i; i = e[i].ne)
    {
        ll v = e[i].v;
        if(v == son[u] || v == fa[u]) continue;

        dfs2(v, v);
    }
}

inline void update(ll u, ll l, ll r, ll k)
{
    if(l <= tr[u].l && r >= tr[u].r)
    {
        tr[u].tag += k, tr[u].minn += k; // 依据题目而不同
        return ;
    }

    pushdown(u); // 如果是区间修改且有懒标记的话,递归前要pushdown

    ll mid = tr[u].l + tr[u].r >> 1; // 注意别写成 l + r >> 1

    if(l <= mid) update(u << 1, l, r, k);
    if(r > mid) update(u << 1 | 1, l, r, k);

    pushup(u);
}

inline ll query(ll u, ll l, ll r)
{
    ll res = 1e17; // 依据题目求最大值最小值区间和而不同
    if(l <= tr[u].l && r >= tr[u].r) return tr[u].minn; // 同上

    pushdown(u);

    ll mid = tr[u].l + tr[u].r >> 1; // 注意同上

    if(l <= mid) res = min(res, query(u << 1, l, r));
    if(r > mid) res = min(res, query(u << 1 | 1, l, r)); // 同上

    return res;
}

inline void upd_path(ll u, ll v, ll k)
{
    while(top[u] != top[v])
    {
        if(dep[top[u]] < dep[top[v]]) swap(u, v);
        update(1, id[top[u]], id[u], k);
        u = fa[top[u]];
    }

    if(dep[u] < dep[v]) swap(u, v);

    update(1, id[v], id[u], k);
}

inline ll query_path(ll u, ll v) // 注释的查询路径的东西因题目而不同
{
    ll res = 1e17; // 

    while(top[u] != top[v])
    {
        if(dep[top[u]] < dep[top[v]]) swap(u, v);
        res = min(res, query(1, id[top[u]], id[u])); // query函数一样,但是求和还是最大值还是最小值不同
        u = fa[top[u]];
    }

    if(dep[u] < dep[v]) swap(u, v);

    res = min(res, query(1, id[v], id[u])); // 同上

    return res;
}

inline void upd_tree(ll u, ll k) // 修改子树
{
    update(1, id[u], id[u] + sz[u] - 1, k); 
}

inline ll que_tree(ll u) // 查询子树
{
    return query(1, id[u], id[u] + sz[u] - 1);
}