代码模板
代码模板
数据结构
ST 表
定义:
int s[MAXN][MAXLOG];
预处理(此处 max 函数可以是任何满足可重复贡献性的函数,如 min ,gcd ):
slim = (int)(log((double)n) / log(2.0));
for (int i = 1; i <= n; i++)
s[i][0] = a[i];
for (int j = 1; j <= slim; j++)
for (int i = 1; i <= n; i++)
if (i + (1 << (j - 1)) <= n)
s[i][j] = max(s[i][j - 1], s[i + (1 << (j - 1))][j - 1]);
查询(有关 max 函数同上):
int query(int l, int r)
{
int qlim = (int)(log((double)(r - l + 1)) / log(2.0));
return max(s[l][qlim], s[r - (1 << qlim) + 1][qlim]);
}
树状数组
基本操作:
int c[maxn];
void add(int x, int k)
{
for (; x <= n; x += x & -x)
c[x] += k;
}
int ask(int x)
{
int k = 0;
for (; x; x -= x & -x)
k += c[x];
return k;
}
倍增(以 kth 为例):
int kth(int x)
{
int p = 0;
for (int k = maxlog; k >= 0; k--)
{
p += (1 << k);
if (p > n || c[p] >= x)
p -= (1 << k);
else
x -= c[p];
}
return p + 1;
}
倍增的思想就是如果超出就回退,在上述示例的代码中,位置如果超出范围或者大小太大就执行回退。
线段树
以洛谷模板为例。
建树:
long long a[maxn];
long long d[maxn*4], z[maxn*4];
void build(int id, int l, int r)
{
if(l == r)
{
d[id] = a[l];
return;
}
int m = (l+r)/2;
build(id<<1, l, m);
build(id<<1|1, m+1, r);
d[id] = d[id<<1] + d[id<<1|1];
}
建树时只涉及 pushup 操作,而不涉及 pushdown 操作。
下面是其他操作的代码:
void add(int id, int l, int r, int ql, int qr, long long k)
{
if(ql <= l && r <= qr)
{
d[id] += 1LL * (r-l+1) * k;
z[id] += k;
return;
}
int m = (l+r)/2;
if(z[id] && l != r)
{
d[id<<1] += z[id] * (m-l+1);
d[id<<1|1] += z[id] * (r-m);
z[id<<1] += z[id];
z[id<<1|1] += z[id];
z[id] = 0;
}
if(ql <= m)
add(id<<1, l, m, ql, qr, k);
if(m < qr)
add(id<<1|1,m+1,r,ql,qr, k);
d[id] = d[id<<1] + d[id<<1|1];
}
long long ask(int id, int l, int r, int ql, int qr)
{
if(ql <= l && r <= qr)
return d[id];
int m = (l+r)/2;
long long sum = 0;
if(z[id] && l != r)
{
d[id<<1] += z[id] * (m-l+1);
d[id<<1|1] += z[id] * (r-m);
z[id<<1] += z[id];
z[id<<1|1] += z[id];
z[id] = 0;
}
if(ql <= m)
sum += ask(id<<1, l, m, ql, qr);
if(m < qr)
sum += ask(id<<1|1,m+1,r,ql,qr);
return sum;
}
pushdown 操作的性质是操作前后,树的本质不变。只要涉及从上往下的访问,都需要进行 pushdown 操作。而 pushup 操作只有在涉及修改的情况下需要进行。特别的是,单纯 pushdown 之后不需要 pushup。
动态开点线段树
动态开点线段树不提前建树,而是在访问每个节点的时候判断当前节点是否建立,如果没有则新开这个节点。以 P3369 【模板】普通平衡树 为例。
int d[maxn], cnt=1, rt=1;
int ls[maxn], rs[maxn];
void add(int &id, int l, int r, int x, int k)
{
if(!id)
id=++cnt;
if(l==r)
{
d[id]+=k;
return;
}
int m = (l+r)>>1;
if(x<=m)
add(ls[id],l,m,x,k);
else
add(rs[id],m+1,r,x,k);
d[id] = d[ls[id]] + d[rs[id]];
}
int ask(int id, int l, int r, int ql, int qr)
{
if(!id)
return 0;
if(ql <= l && r <= qr)
return d[id];
int m = (l+r)>>1, sum = 0;
if(ql <= m)
sum += ask(ls[id], l, m, ql, qr);
if(m < qr)
sum += ask(rs[id], m+1,r,ql, qr);
return sum;
}
int kth(int id, int l, int r, int k)
{
if(!id)
return -1;
if(l == r)
return l;
int m = (l+r)>>1;
if(k <= d[ls[id]])
return kth(ls[id], l, m, k);
else
return kth(rs[id], m+1,r,k-d[ls[id]]);
}
动态开点线段树往往需要应对负数区间,注意在除以 2 的时候使用右移运算符,而不要使用除法运算符,因为我们需要的是“向下取整”,不是“向零取整”。C++ 的 / 运算符是向零取整的。
Splay 树
目前我使用的 Splay 是指针版的。
struct node
{
node *ch[2];
int val, cnt, siz;
node(int v) : ch{nullptr, nullptr}, val(v), cnt(1), siz(1) {}
void pushup() { siz = (ch[0] == nullptr ? 0 : ch[0]->siz) + cnt + (ch[1] == nullptr ? 0 : ch[1]->siz); }
int find(int v) { return v == val ? -1 : v > val; }
int chsiz(int x) { return ch[x] == nullptr ? 0 : ch[x]->siz; }
} *root = nullptr;
void rotate(node *&cur, bool type)
{
node *rt = cur->ch[type];
cur->ch[type] = rt->ch[!type], rt->ch[!type] = cur, cur = rt;
cur->ch[!type]->pushup(), cur->pushup();
}
void splay(node *&cur, int val)
{
int type1 = cur->find(val);
if (type1 == -1 || cur->ch[type1] == nullptr)
return;
int type2 = cur->ch[type1]->find(val);
if (type2 == -1 || cur->ch[type1]->ch[type2] == nullptr)
{
rotate(cur, type1);
return;
}
splay(cur->ch[type1]->ch[type2], val);
if (type1 == type2)
rotate(cur, type1), rotate(cur, type2);
else
rotate(cur->ch[type1], type2), rotate(cur, type1);
}
图论
树的重心
int root, siz[maxn], minch, tsiz;
void get_root(int u, int fa)
{
siz[u] = 1;
int maxch = 0;
for(int i=head[u];i;i=nxt[i])
{
int v = to[i];
if(v != fa && !blocked[v])
{
get_root(v, u);
siz[u] += siz[v];
maxch = max(maxch, siz[v]);
}
}
maxch = max(maxch, tsiz-siz[u]);
if(maxch < minch)
minch = maxch, root = u;
}
链式前向星存图
int head[MAXN], nxt[MAXM], to[MAXM], cnt;
void add(int u, int v)
{
nxt[++cnt]=head[u];
head[u]=cnt;
to[cnt]=v;
}
从 1 开始,预处理为 0;从 0 开始,预处理为 -1。
图的连通性
缩点
int dfn[MAXN], low[MAXN], dcnt;
int s[MAXN], ins[MAXN], scnt;
int belong[MAXN], bs[MAXN], bcnt;
void dfs(int u)
{
dfn[u] = low[u] = ++dcnt;
s[++scnt] = u;
ins[u] = 1;
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(!dfn[v])
{
dfs(v, u);
low[u] = min(low[u], low[v]);
}
if(ins[v])
low[u] = min(low[u], dfn[v]);
}
if(dfn[u] == low[u])
{
bcnt++;
while(s[scnt] != u)
ins[s[scnt]] = 0, bs[bcnt]++, belong[s[scnt--]]=bcnt;
ins[s[scnt]] = 0, bs[bcnt]++, belong[s[scnt--]]=bcnt;
}
}
不建议写 if(dfn[v] < dfn[u] && v != fa),因为只有当图连通时才正确。
重链剖分
树上启发式合并的预处理
void predfs(int u, int fa)
{
dep[u] = dep[fa] + 1;
siz[u] = 1;
for (int i = head[u]; i; i = nxt[i])
{
int v = to[i];
if (v != fa)
{
predfs(v, u);
siz[u] += siz[v];
if (!heavy[u] || siz[heavy[u]] < siz[v])
heavy[u] = v;
}
}
}

浙公网安备 33010602011771号