板子4
主席树(求区间第K小)
int cnt, T[maxn];
struct Tree{
int l, r, val;
} tre[maxn*40];
void init() {
cnt = 0;
}
int build(int l, int r) {
int nod = ++cnt;
tre[nod].val = 0;
if (l == r) return nod;
int m = l + r >> 1;
tre[nod].l = build(l, m);
tre[nod].r = build(m+1, r);
return nod;
}
int update(int pre, int l, int r, int val) {
int nod = ++cnt;
tre[nod] = tre[pre]; tre[nod].val++;
if (l == r) return nod;
int m = l + r >> 1;
if (val <= m) tre[nod].l = update(tre[pre].l, l, m, val);
else tre[nod].r = update(tre[pre].r, m+1, r, val);
return nod;
}
int query(int v, int u, int l, int r, int k) {
if (l == r) return l;
int m = l + r >> 1;
int num = tre[tre[u].l].val - tre[tre[v].l].val; //左边的数多少
if (num >= k) return query(tre[v].l, tre[u].l, l, m, k);
else return query(tre[v].r, tre[u].r, m + 1, r, k - num);
}
int a[maxn], n, m, b[maxn];
void solve() {
while (cin >> n >> m) {
init();
for (int i = 1; i <= n; i++) {
scanf("%d", a + i);
b[i] = a[i];
}
sort(b+1, b+1+n);
int xx = unique(b+1, b+1+n) - (b + 1); //离散
T[0] = build(1, xx); //根节点
for (int i = 1; i <= n; i++) {
int h = lower_bound(b+1, b+1+xx, a[i]) - (b + 1);
T[i] = update(T[i-1], 1, xx, h+1);
}
while (m--) {
int l, r, k; scanf("%d%d%d", &l, &r, &k);
printf("%d\n", b[query(T[l-1], T[r], 1, xx, k)]);
}
}
}
后缀数组(求重复子串的个数)
char s[maxn];
int sa[maxn], t[maxn], t2[maxn], tt[maxn], c[maxn], ss[maxn];
int Rank[maxn], height[maxn];
bool cmp(int *r, int a, int b, int l) {
return r[a] == r[b] && r[a+l] == r[b+l];
}
// 名次数组:名次数组 Rank[i]保存的是 Suffix(i)在所有后缀中从小到大排列的“名次”。
/*后缀数组:后缀数组 SA 是一个一维数组,它保存 1..n 的某个排列 SA[1],
SA[2],......,SA[n],并且保证 Suffix(SA[i]) < Suffix(SA[i+1]),1≤i<n。
也就是将 S 的 n 个后缀从小到大进行排序之后把排好序的后缀的开头位置顺
次放入 SA 中。*/
//SA数组最后1-len代表的是所有的下标、SA[0]是添加的末尾
//height是1-len代表 定义 height[i]=suffix(sa[i-1])和 suffix(sa[i])的最长公共前缀,也就是排名相邻的两个后缀的最长公共前缀。
//一定要记住下标为0 的是添加的0,height和SA都是
void build_sa(int *r, int n, int m) {
int *x = t, *y = t2;
for (int i = 0; i < m; i++) c[i] = 0;
for (int i = 0; i < n; i++) c[x[i] = r[i]]++;
for (int i = 1; i < m; i++) c[i] += c[i-1];
for (int i = n - 1; i >= 0; i--) sa[--c[x[i]]] = i;
for (int j = 1; ; j <<= 1) {
int p = 0;
for (int i = n - j; i < n; i++) y[p++] = i;
for (int i = 0; i < n; i++) if (sa[i] >= j) y[p++] = sa[i] - j;
for (int i = 0; i < n; i++) tt[i] = x[y[i]];
for (int i = 0; i < m; i++) c[i] = 0;
for (int i = 0; i < n; i++) c[tt[i]]++;
for (int i = 1; i < m; i++) c[i] += c[i-1];
for (int i = n - 1; i >= 0; i--) sa[--c[tt[i]]] = y[i];
swap(x, y), p = 1, x[sa[0]] = 0;
for (int i = 1; i < n; i++)
x[sa[i]] = cmp(y, sa[i-1], sa[i], j) ? p - 1 : p++;
if (p >= n) break;
m = p;
}
}
void getHeight(int *r, int n) {
int k = 0;
for (int i = 1; i <= n; i++) Rank[sa[i]] = i;
for (int i = 0; i < n; i++) {
if (k) k--;
int j = sa[Rank[i] - 1];
while (r[i+k] == r[j+k]) k++;
height[Rank[i]] = k;
}
}
void solve() {
scanf("%s", s);
int len = strlen(s);
for (int i = 0; i < len; i++)
ss[i] = s[i];
ss[len] = 0; //使用ss代替s,并且在最后添加一个0构成 s + '0'
build_sa(ss, len+1, 256);
getHeight(ss, len);
int ans = 0;
for (int i = 1; i <= len; i++) {
ans += height[i] > height[i-1] ? height[i] - height[i-1] : 0;
}
cout << ans << endl;
}
树的点分治
//树的点分治,该代码是求树上有多少对点的dis<=k
//vis代表该点是否已经当做过重心,siz是子树节点个数、mv是子树中最大的节点数
int n, cnt, head[maxn], k, vis[maxn], root, maxx, dis[maxn];
int ans, num, tot, siz[maxn], mv[maxn]; //tot代表当前树的节点个数、如果遇到时间过长,考虑重心是否找对
struct Edge {
int v, w, next;
} e[maxn<<1];
void init() {
ans = cnt = 0;
memset(vis, 0, sizeof(vis));
memset(head, -1, sizeof(head));
}
void add(int u, int v, int w) {
e[cnt].v = v;
e[cnt].w = w;
e[cnt].next = head[u];
head[u] = cnt++;
}
void getroot(int u, int fa) {
siz[u] = 1, mv[u] = 0;
for (int i = head[u]; ~i; i = e[i].next) {
int v = e[i].v;
if (v == fa || vis[v]) continue;
getroot(v, u);
siz[u] += siz[v];
mv[u] = max(mv[u], siz[v]);
}
mv[u] = max(mv[u], tot - siz[u]);
if (mv[u] < mv[root])
root = u;
}
void getdis(int u, int fa, int dep) {
dis[num++] = dep;
for (int i = head[u]; ~i; i = e[i].next) {
int v = e[i].v;
if (v == fa || vis[v]) continue;
getdis(v, u, dep + e[i].w);
}
}
int cal(int u, int f) {
int res = 0;
num = 0;
getdis(u, -1, f);
sort(dis, dis + num);
int r = num - 1;
for (int l = 0; l < r; l++) {
while (dis[l] + dis[r] > k && l < r)
r--;
res += r - l;
}
return res;
}
void work(int u) {
vis[u] = 1;
ans += cal(u, 0);
for (int i = head[root]; ~i; i = e[i].next) {
int v = e[i].v;
if (vis[v]) continue;
ans -= cal(v, e[i].w);
mv[root=0] = tot = siz[v];
getroot(v, -1);
work(root);
}
}
void solve() {
while (cin >> n >> k) {
if (!n && !k) break;
init();
for (int i = 1; i < n; i++) {
int u, v, w; scanf("%d%d%d", &u, &v, &w);
add(u, v, w); add(v, u, w);
}
mv[root=0] = tot = n;
getroot(1, -1);
work(root);
cout << ans << endl;
}
}
无源汇有上下界的网络流
将原弧(u,v)分离出一条必要弧和一条非必要弧: 假设B(u,v)是下界,则分离出两条弧: C1(u,v) = B(u,v) -- 必要弧 C2(u,v) = C(u,v) – B(u,v)
添加附加点x,y。想像一条不限上界的(x, y),用必 要弧将它们“串”起来,即对于有向必要弧(u, v),添加 (u, x),(y, v),容量为必要弧容量。这样就建立了一个等 价的网络。
即建图为:
AddEdge(u[i], v[i], f[i] - l[i]); //l[i]代表下界,f[i]代表上界 AddEdge(s, v[i], l[i]); AddEdge(u[i], t, l[i]);若此图上的最大流能够占满与Y相连的所有边的容量(自 然也就会占满所有连到x的边的容量),那么原图上就存 在满足上下界条件的可行流。若最大流不能够占满与Y相 连的所有边的容量,则原图不存在可行流。
动态节点的线段树
int T[maxn], L[maxn], R[maxn], val[maxn], tot, flag;
void update(int &id, int l, int r, int pos, int x) {
if (!id) {
id = ++tot;
val[id] = x;
}
if (val[id] > x) val[id] = x;
if (l == r) return;
int mid = l + r >> 1;``
if (pos <= mid) update(L[id], l, mid, pos, x);
else update(R[id], mid + 1, r, pos, x);
}
void query(int id, int l, int r, int ql, int qr, int x) {
if (!id || flag) return;
if (ql <= l && qr >= r) {
if (val[id] <= x) flag = 1;
return;
}
int mid = l + r >> 1;
if (ql <= mid) query(L[id], l, mid, ql, qr, x);
if (qr > mid) query(R[id], mid + 1, r, ql, qr, x);
}
void init() {
tot = 0;
memset(T, 0, sizeof(T));
memset(L, 0, sizeof(L));
memset(R, 0, sizeof(R));
}
最小费用最大流
int cnt;
int n, m, s, t;
bool vis[maxn];
ll d[maxn], a[maxn]; //a -> 当前可以的流量 d -> 花费,类似距离
int cur[maxn], pre[maxn]; //pre -> 上一条弧
struct Edge {
int u, v;
ll cap, flow;
ll cost;
} e[maxn*16]; //因为是双向边 所以记得开二倍
vector<int> G[maxn];
void init(int tot) {
for (int i = 0; i <= tot; i++) //一定不要对vector使用memset!!!!!
G[i].clear();
cnt = 0;
}
void add(int u, int v, ll cap, ll f, ll cost) {
e[cnt].u = u;
e[cnt].cap = cap;
e[cnt].flow = f;
e[cnt].v = v;
e[cnt].cost = cost;
}
void AddEdge(int u, int v, ll cap, ll cost) {
add(u, v, cap, 0, cost);
G[u].push_back(cnt++);
add(v, u, 0, 0, -cost);
G[v].push_back(cnt++);
}
bool SPFA(ll& flow, ll& cost, int tot) {
for (int i = 0; i <= tot; i++) d[i] = INF;
memset(vis, 0, sizeof(vis));
d[s] = 0, vis[s] = 1, pre[s] = 0, a[s] = INF;
queue<int> q;
q.push(s);
while (q.size()) {
int u = q.front(); q.pop();
vis[u] = 0;
for (int i = 0; i < G[u].size(); i++) {
Edge& v = e[G[u][i]];
if (v.cap > v.flow && d[v.v] > d[u] + v.cost) {
d[v.v] = d[u] + v.cost;
pre[v.v] = G[u][i];
a[v.v] = min(a[u], v.cap - v.flow);
if (!vis[v.v]) {
q.push(v.v);
vis[v.v] = 1;
}
}
}
}
if (d[t] == INF) return false;
flow += a[t];
cost += d[t] * a[t];
for (int u = t; u != s; u = e[pre[u]].u) {
e[pre[u]].flow += a[t];
e[pre[u]^1].flow -= a[t];
}
return true;
}
ll MincostMaxflow(ll& cost, int tot) {
ll flow = 0; cost = 0;
while (SPFA(flow, cost, tot));
return flow;
}
区间合并线段树(入门题的, 带修改的最长连续1串的长度)
struct Tree {
int l, r, ls, rs, val; //ls -> 代表左边连续的1, rs代表右边连续的1, val代表当前区间最长的连续1
} tre[maxn<<2];
void build(int id, int l, int r) {
tre[id].l = l, tre[id].r = r, tre[id].val = tre[id].ls = tre[id].rs = 1;
if (l == r) return;
int mid = l + r >> 1;
build(id<<1, l, mid);
build(id<<1|1, mid + 1, r);
tre[id].ls = tre[id].rs = tre[id].val = tre[id<<1].val + tre[id<<1|1].val;
}
void update(int id, int pos, int f) {
if (tre[id].l == tre[id].r) {
tre[id].val = tre[id].ls = tre[id].rs = f;
return;
}
int mid = tre[id].l + tre[id].r >> 1;
if (mid >= pos) update(id<<1, pos, f);
else update(id<<1|1, pos, f);
tre[id].ls = tre[id<<1].ls;
if (tre[id<<1].ls == tre[id<<1].r - tre[id<<1].l + 1) tre[id].ls += tre[id<<1|1].ls; //如果左子节点全部都是连续的1
tre[id].rs = tre[id<<1|1].rs;
if (tre[id<<1|1].rs == tre[id<<1|1].r - tre[id<<1|1].l + 1) tre[id].rs += tre[id<<1].rs; //如果右子节点全部是连续的1
tre[id].val = max(tre[id<<1].rs + tre[id<<1|1].ls, max(tre[id<<1].val, tre[id<<1|1].val));
}
int query(int id, int pos) {
if (tre[id].l == tre[id].r) return tre[id].val;
int mid = tre[id].l + tre[id].r >> 1;
if (pos <= mid) {
if (pos >= tre[id<<1].r - tre[id<<1].rs + 1) return tre[id<<1|1].ls + tre[id<<1].rs; //如果当前位置所在的位置是在左子节点最右边的连续(1)段中
return query(id<<1, pos);
}
else {
if (pos <= tre[id<<1|1].l + tre[id<<1|1].ls - 1) return tre[id<<1].rs + tre[id<<1|1].ls; //类似上面的
return query(id<<1|1, pos);
}
}
数位DP
ll dp[40][40][40];
int a[40];
//数位DP最主要的是记忆化! 比如当前的状态只要前面够成过就好啦,比如当前题, 对于当前位置来说,只要当前的1的个数和2的个数是出现过的,那么就代表
//当前状态是出现过的,就可以直接记录啦!
ll dfs(int pos, int z, int y, bool limit, bool lead) { //z和y分别代表0和1的个数
if (pos == -1) {
return z >= y; //枚举到结尾看是否合法, 二进制中1的个数大于2的个数
}
if (!limit && dp[pos][z][y] != -1) return dp[pos][z][y];
int up = limit ? a[pos] : 1;
ll ans = 0;
for (int i = 0; i <= up; i++) {
int z_n = z;
if (!lead && !i) z_n++;
ans += dfs(pos - 1, z_n, y + (i == 1), limit & i == a[pos], lead & (i == 0));
}
if (!limit) dp[pos][z][y] = ans;
return ans;
}
ll gao(ll x) {
int pos = 0;
while (x) {
a[pos++] = x & 1;
x >>= 1;
}
return dfs(pos - 1, 0, 0, true, true);
}
void solve() {
ll l, r;
memset(dp, -1, sizeof(dp));
while (cin >> l >> r) {
cout << gao(r) - gao(l - 1) << endl;
}
}
二分图的最小点权覆盖:
将原图的边权赋为INF,然后增加超级源汇点,S连左半图,左半图连T,权值为点上的值
二分图的最大点权独立集
所有节点的值减去二分图的最小点权覆盖
最大权闭合图
在原图点集的基础上增加源 和汇 s t ;将原图每条有向边的容量替换为INF,然后s连所有权值为正的点,边的权值为点的权值,所有权值为负的点连t,权值为点权值的绝对值,最后所有权值为正的和减去最小割即为所求
最大密度子图
转化图:

然后就转化成了求最大权闭合图 h ( g ) = max ⎨ ∑ 1 ⋅ x e − ∑ g ⋅ x v ⎬ 其中g是答案 二分g, 如果h(g) <= 0 (其实是 == 0, 因为最大权闭合图最小为0?) 说明g偏大 r = mid; 否则 l = mid;
double的Dinic模板, 求最大密度子图(本题是选取一些数,使得逆序数 / 选取的数的个数 最大 注意比较大小都要有sgn! 不然各种错误!)
const db eps = 1e-7;
int head[maxn], cnt;
int n, m, s, t;
bool vis[maxn];
int d[maxn], cur[maxn];
struct Edge {
int u, v;
db cap, flow;
} e[maxn*40]; //因为是双向边 所以记得开二倍
vector<int> G[maxn];
int sgn(db x) {
return x < -eps ? -1 : x < eps ? 0 : 1;
}
void init() {
memset(head, -1, sizeof(head));
for (int i = 0; i < maxn; i++) //一定不要对vector使用memset!!!!!
G[i].clear();
cnt = 0;
}
void add(int u, int v, db cap, db f) {
e[cnt].u = u;
e[cnt].cap = cap;
e[cnt].flow = f;
e[cnt].v = v;
}
void AddEdge(int u, int v, db cap) {
add(u, v, cap, 0);
G[u].push_back(cnt++);
add(v, u, 0, 0);
G[v].push_back(cnt++);
}
bool BFS() {
memset(vis, 0, sizeof(vis));
queue<int> q;
q.push(s);
vis[s] = 1;
d[s] = 0;
while (!q.empty()) {
int v = q.front(); q.pop();
for (int i = 0; i < G[v].size(); i++) {
Edge &te = e[G[v][i]];
if (!vis[te.v] && sgn(te.cap - te.flow) > 0) { //只考虑残量网络的弧
vis[te.v] = 1;
d[te.v] = d[v] + 1;
q.push(te.v);
}
}
}
return vis[t];
}
db dfs(int x, db a) {
if (x == t || sgn(a) == 0) return a;
db flow = 0, f;
for (int &i = cur[x]; i < G[x].size(); i++) { //从上次考虑的弧
Edge &te = e[G[x][i]];
if (d[x] + 1 == d[te.v] && sgn(f = dfs(te.v, min(a, te.cap - te.flow))) > 0) {
te.flow += f;
e[G[x][i]^1].flow -= f;
flow += f;
a -= f;
if (sgn(a) == 0) break;
}
}
return flow;
}
db Dinic() {
db flow = 0;
while (BFS()) {
memset(cur, 0, sizeof(cur));
flow += dfs(s, INF);
}
return flow;
}
int a[maxn];
void solve() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
db l = 0, r = n * n;
while (sgn(l - r) < 0) {
db g = (l + r) / 2.0;
db num = 0; init();
s = 0, t = maxn - 1;
int cnt = n;
for (int i = 1; i <= n; i++) {
AddEdge(i, t, g);
for (int j = 1; j < i; j++) {
if (a[j] > a[i]) {
num++; cnt++;
AddEdge(s, cnt, 1);
AddEdge(cnt, i, INF);
AddEdge(cnt, j, INF);
}
}
}
db tmp = Dinic();
if (sgn(num - tmp) <= 0) r = g;
else l = g;
}
printf("%.10f\n", l);
}
二进制的高维前缀:
for (int i = 0; i < k; i++) {
for (int j = 0; j < (1 << k); j++)
if (!((1 << i) & j)) dp[j] += dp[j | (1 << i)]; // 当前的表示包括本身的超集的值的和
//比如dp[100] = dp[100] + dp[101] + dp[110] + dp[111];
}
AC自动机(当前题是取一个最长的串,看其他串是否都为最长串的子串)
int ch[maxnNode][26], cnt;
int val[maxnNode], f[maxnNode], last[maxn];
struct Ac {
int sz;
Ac() {
sz = 1; memset(val, 0, sizeof(val));
memset(ch[0], 0, sizeof(ch[0]));
}
int idx(char c) { return c - 'a'; }
void Insert(char *s, int v) {
int u = 0, n = strlen(s);
for (int i = 0; i < n; i++) {
int c = idx(s[i]);
if (!ch[u][c]) {
memset(ch[sz], 0, sizeof(ch[sz]));
val[sz] = 0;
ch[u][c] = sz++;
}
u = ch[u][c];
}
val[u] += v;
//字符串的字符的附加信息
}
void getFaile() {
queue<int> q;
f[0] = 0;
for (int c = 0; c < 26; c++) {
int u = ch[0][c];
if (u) {
f[u] = 0; q.push(u);
last[u] = 0;
}
}
while (!q.empty()) {
int r = q.front(); q.pop();
for (int c = 0; c < 26; c++) {
int u = ch[r][c];
if (!u) continue;
q.push(u);
int v = f[r];
while (v && !ch[v][c]) v = f[v];
f[u] = ch[v][c];
last[u] = val[f[u]] ? f[u] : last[f[u]];
}
}
}
void cal(int j) {
while (j) {
cnt += val[j];
val[j]=0;
j = last[j];
}
}
void Find(char *T) {
int n = strlen(T);
int j = 0;
for (int i = 0; i < n; i++) {
int c = idx(T[i]);
while (j && !ch[j][c]) j = f[j];
j = ch[j][c];
if (val[j]) cal(j);
else if (last[j]) cal(last[j]);
}
}
};
char s[maxn];
void solve() {
int n; cin >> n;
Ac ac;
int mlen = 0, k, clen = 0;
for (int i = 1; i <= n; i++) {
scanf("%s", s + clen);
ac.Insert(s + clen, 1);
int len = strlen(s + clen);
if (len > mlen) {
mlen = len;
k = clen;
}
clen += len;
}
ac.getFaile();
s[k + mlen] = 0;
cnt = 0;
ac.Find(s + k);
if (cnt == n) puts(s + k);
else cout << "No\n";
}
二进制子集的前缀:
for (int j = 0; j < m; j++)
for (int k = 0; k < (1 << m); k++)
if (((1 << j) & k)) dp[i-1][k] = (dp[i-1][k] + dp[i-1][k ^ (1 << j)]) % mod;

浙公网安备 33010602011771号