矩阵
矩乘(现在正确, 20191021)
const LL MOD = 1e9 + 7; struct Matrix { int row, col; LL val[33][33]; void init0() { memset(val, 0, sizeof val); } void init1() { memset(val, 0, sizeof val); for (int i = 0; i < row; ++ i) val[i][i] = 1; } Matrix () {} Matrix (int _row, int _col) { row = _row; col = _col; memset(val, 0, sizeof val); } Matrix operator * (const Matrix B) const { Matrix C (row, B.col); for (int i = 0; i < row; ++ i) for (int j = 0; j < B.col; ++ j) for (int k = 0; k < col; ++ k) (C.val[i][j] += val[i][k] * B.val[k][j] % MOD) %= MOD/*, printf("%d%d += %lld * %lld\n", i, j, val[i][k], val[k][j])*/; return C; } void print() { for (int i = 0; i < row; ++ i, puts("")) for (int j = 0; j < col; ++ j) printf("%lld ", val[i][j]); } } F, T; Matrix fpow(Matrix A, LL b) { Matrix ret (A.row, A.col); ret.init1(); while (b) { if (b & 1) b ^= 1, ret = ret * A; else b >>= 1, A = A * A; } return ret; }
高斯消元
void Gauss(int n) { int r = 1, c = 1, maxr = 0; for (; r <= n && c <= n; ++ r, ++ c) { maxr = r; for (int i = r + 1; i <= n; ++ i) if (fabs(a[i][c]) - fabs(a[maxr][c]) > EPS) maxr = i; if (maxr != r) for (int i = 1; i <= n + 1; ++ i) // n + 1 !!! swap(a[r][i], a[maxr][i]); if (fabs(a[r][c]) < EPS) { -- r; continue; } for (int i = r + 1; i <= n; ++ i) { double k = a[i][c] / a[r][c]; for (int j = c; j <= n + 1; ++ j) a[i][j] = a[i][j] - k * a[r][j]; } } -- r, -- c; for (int i = n; i >= 1; -- i) { int all0 = 1; for (int j = i; j <= n; ++ j) all0 &= (fabs(a[i][j]) < EPS); if (all0) { if (fabs(a[i][n + 1]) > EPS) nosol = true; else mulsol = true; } else { for (int j = i + 1; j <= n; ++ j) a[i][n + 1] -= ansx[j] * a[i][j]; ansx[i] = a[i][n + 1] / a[i][i]; } } } int main() { n = in(); for (int i = 1; i <= n; ++ i) for (int j = 1; j <= n + 1; ++ j) a[i][j] = in(); Gauss(n); if (nosol) return puts("-1"), 0; if (mulsol) return puts("0"), 0; for (int i = 1; i <= n; ++ i) printf("x%d=%.2f\n", i, ansx[i]); return 0; }
建图
int head[MAXN], nume; struct Adj { int nex, to, w; } adj[MAXM << 1]; void addedge(int from, int to, int w) { adj[++ nume] = (Adj) { head[from], to, w }; head[from] = nume; } void link(int u, int v, int w) { addedge(u, v, w); addedge(v, u, w); }
Tarjan 边双缩点
int dfn[MAXN], low[MAXN], idx; bool cut[MAXM << 1]; void Tarjan(int u, int fa) { dfn[u] = low[u] = ++ idx; for (int i = head[u]; i != -1; i = adj[i].nex) { int v = adj[i].to; if ((i ^ 1) == fa) continue; if (dfn[v]) { low[u] = min(low[u], dfn[v]); } else { Tarjan(v, i); low[u] = min(low[u], low[v]); } } if (fa != -1 && low[u] > dfn[adj[fa ^ 1].to]) cut[fa] = cut[fa ^ 1] = true; } int bel[MAXN], cnt; void DFS(int u, int fa) { bel[u] = cnt; for (int i = head[u]; i != -1; i = adj[i].nex) { if ((i ^ 1) == fa || cut[i]) continue; int v = adj[i].to; if (bel[v]) continue; DFS(v, i); } } void traverse() { for (int i = 1; i <= n; ++ i) if (!dfn[i]) Tarjan(i, -1); for (int i = 1; i <= n; ++ i) if (!bel[i]) { ++ cnt; DFS(i, -1); } } int dgr[MAXN]; int main() { n = in(); m = in(); memset(head, -1, sizeof head); for (int i = 1; i <= m; ++ i) { int u = in(), v = in(); link(u, v); } traverse(); for (int u = 1; u <= n; ++ u) { for (int i = head[u]; i != -1; i = adj[i].nex) { int v = adj[i].to; if (bel[u] == bel[v]) continue; ++ dgr[bel[u]], ++ dgr[bel[v]]; } } int ans = 0; for (int i = 1; i <= cnt; ++ i) if (dgr[i] == 2) ++ ans; printf("%d\n", (ans + 1) / 2); return 0; }
Tarjan 点双缩点
int idx, low[MAXN], dfn[MAXN]; bool cut[MAXN], nocut; void Tarjan(int u, int fa) { low[u] = dfn[u] = ++ idx; int ch = 0; for (int i = head[u]; i != -1; i = adj[i].nex) { if ((fa ^ 1) == i) continue; int v = adj[i].to; if (!dfn[v]) { Tarjan(v, i); low[u] = min(low[u], low[v]); if (fa == -1) ++ ch; if ((fa != -1 && low[v] >= dfn[u]) || (fa == -1 && ch > 1)) cut[u] = true, nocut = false; } else low[u] = min(low[u], dfn[v]); } } int top, stk[MAXN]; void DFS(int u, int fa) { stk[++ top] = u; dfn[u] = 1; if (cut[u]) return; for (int i = head[u]; i != -1; i = adj[i].nex) { int v = adj[i].to; if ((fa ^ 1) == i || dfn[v]) continue; // printf("%d --> %d\n", u ,v); DFS(v, i); } } void init() { nume = 0; memset(head, -1, sizeof head); n = 0; ans1 = 0; ans2 = 1; idx = 0; memset(low, 0, sizeof low); memset(dfn, 0, sizeof dfn); memset(cut, false, sizeof cut); nocut = true; } int main() { int t = 0; while (true) { m = in(); if (!m) break; init(); printf("Case %d: ", ++ t); for (int i = 1; i <= m; ++ i) { int u = in(), v = in(); n = max(n, max(u, v)); link(u, v); } for (int i = 1; i <= n; ++ i) if (!dfn[i]) Tarjan(i, -1); memset(dfn, 0, sizeof dfn); for (int i = 1; i <= n; ++ i) if (!cut[i] && !dfn[i]) { int numcut = 0; top = 0; DFS(i, -1); for (int j = 1; j <= top; ++ j) if (cut[stk[j]]) ++ numcut, dfn[stk[j]] = 0; if (numcut == 1) ++ ans1, ans2 *= 1LL * (top - 1); } if (nocut) printf("2 %lld\n", 1LL * n * (n - 1) / 2); else printf("%lld %lld\n", ans1, ans2); } return 0; }
Tarjan 强连通缩点
int dfn[MAXN], mn[MAXN], ind; int stk[MAXN], top, belong[MAXN], cnt; bool instk[MAXN]; void Tarjan(int u) { dfn[u] = mn[u] = ++ ind; stk[++ top] = u; instk[u] = true; for (int i = g1.head[u]; i != -1; i = g1.adj[i].nex) { int v = g1. adj[i].to; if (dfn[v]) { if (instk[v]) mn[u] = min(mn[u], dfn[v]); } else { Tarjan(v); mn[u] = min(mn[u], mn[v]); } } if (dfn[u] == mn[u]) { ++ cnt; while (true) { belong[stk[top]] = u; instk[stk[top]] = false; -- top; if (stk[top + 1] == u) break; } } }
01BFS
只有 01 两种边
如果边权为 1 , 则加入队尾, 否则队首
deque <int> q; q.push_front(); q.push_back(); q.front(); q.pop_front();
二分图匹配匈牙利
bool DFS(int u) { for (int i = head[u]; i; i = adj[i].nex) { int v = adj[i].to; if (vis[v]) continue; vis[v] = true; if (!maty[v] || (maty[v] && DFS(maty[v]))) { matx[maty[v] = u] = v; return true; } } return false; } void Maxmatch() { for (int i = 1; i <= n; ++ i) { if (matx[i]) continue; memset(vis, false, sizeof vis); DFS(i); } }
Dinic最大流
int lev[MAXN]; queue <int> Q; bool BFS(int s, int t) { while (!Q.empty()) Q.pop(); memset(lev, 0, sizeof lev); lev[s] = 1; Q.push(s); while (!Q.empty()) { int u = Q.front(); Q.pop(); for (int i = head[u]; i != -1; i = adj[i].nex) { int v = adj[i].to; if (!lev[v] && adj[i].w > 0) { lev[v] = lev[u] + 1; Q.push(v); } } } return lev[t] != 0; } LL DFS(int u, int t, LL flow) { if (u == t) return flow; LL sum = 0; for (int &i = cur[u]; i != -1; i = adj[i].nex) { int v = adj[i].to; if (lev[v] == lev[u] + 1 && adj[i].w) { LL f = DFS(v, t, min(flow, adj[i].w)); adj[i].w -= f; adj[i ^ 1].w += f; sum += f; flow -= f; } } return sum; } LL Dinic() { LL ret = 0; while ( BFS(s, t) ) { copy(head, head + n + 1, cur); ret += DFS(s, t, INF); } return ret; } int main() { nume = -1; memset(head, -1, sizeof head); n = in(), m = in(); s = in(), t = in(); for (int i = 1; i <= m; i ++) { int u = in(), v = in(); LL w = in(); addedge(u, v, w); addedge(v, u, 0); } printf("%lld\n", Dinic()); return 0; }
费用流
LL flow[MAXN], dis[MAXN]; int pre[MAXN], pree[MAXN]; bool vis[MAXN]; queue <int> Q; bool SPFA(int s, int t) { memset(flow, 0x3f3f3f, sizeof flow); memset(dis , 0x3f3f3f, sizeof dis); memset(vis, 0, sizeof vis); memset(pre, -1, sizeof pre); Q.push(s); vis[s] = true; dis[s] = 0; while (!Q.empty()) { int u = Q.front(); Q.pop(); vis[u] = false; for (int i = head[u]; i != -1; i = adj[i].nex) { int v = adj[i].to; if (adj[i].w > 0 && dis[v] > dis[u] + adj[i].c) { dis[v] = dis[u] + adj[i].c; pre[v] = u; pree[v] = i; flow[v] = min(flow[u], adj[i].w); if (!vis[v]) { vis[v] = true; Q.push(v); } } } } return pre[t] != -1; } void MCMF(int s, int t) { while ( SPFA(s, t) ) { maxflow += flow[t]; mincost += flow[t] * dis[t]; int now = t; while (now != s) { adj[pree[now]].w -= flow[t]; adj[pree[now] ^ 1].w += flow[t]; now = pre[now]; } } } int main() { memset(head, -1, sizeof head); nume = -1; n = in(), m = in(), s = in(), t = in(); for (int i = 1; i <= m; i ++) { int u = in(), v = in(); LL w = in(), c = in(); add_di(u, v, w, c); add_di(v, u, 0, -c); } MCMF(s, t); printf("%lld %lld\n", maxflow, mincost); return 0; }
数据结构
线段树
注意: MAXT大小, 懒标记优先关系, 下传/更新
线段树(最值, 极差(后大-前小))
namespace Segt { #define ls (now << 1) #define rs (now << 1 | 1) #define mid ((l + r) >> 1) const int MAXT = MAXN << 2, INF = 1e7; int tag[MAXT]; int mn[MAXT], mx[MAXT], del[MAXT]; void pushup(int now) { mn[now] = min(mn[ls], mn[rs]); mx[now] = max(mx[ls], mx[rs]); del[now] = max(max(del[ls], del[rs]), mx[rs] - mn[ls]); } void addtag(int now, int c) { mn[now] += c; mx[now] += c; tag[now] += c; } void pushdown(int now) { if (tag[now] != 0) addtag(ls, tag[now]), addtag(rs, tag[now]); tag[now] = 0; } void build(int now, int l, int r) { tag[now] = 0; if (l == r) { addtag(now, pre[l]); return; } build(ls, l, mid); build(rs, mid + 1, r); pushup(now); } void modify(int now, int l, int r, int x, int y, int c) { if (x > r || y < l) return ; if (x <= l && r <= y) { addtag(now, c); return ; } pushdown(now); modify(ls, l, mid, x, y, c); modify(rs, mid + 1, r, x, y, c); pushup(now); } Ans query(int now, int l, int r, int x, int y) { if (x > r || y < l) return (Ans){ -INF, INF, -2 * INF }; if (x <= l && r <= y) return (Ans){ mx[now], mn[now], del[now] }; pushdown(now); Ans ql = query(ls, l, mid, x, y), qr = query(rs, mid + 1, r, x, y); return (Ans){ max(ql.mx, qr.mx), min(ql.mn, qr.mn), max(max(ql.del, qr.del), qr.mx - ql.mn) }; } void modify(int x, int y, int c) { modify(1, 0, n, x, y, c); } Ans query(int x, int y) { return query(1, 0, n, x, y); } #undef ls #undef rs #undef mid } using namespace Segt;
树链剖分(预处理)
int siz[MAXN], dep[MAXN]; int idx, dfn[MAXN], fa[MAXN], top[MAXN], hson[MAXN]; void DFS(int u, int ma) { siz[u] = 1; fa[u] = ma; dep[u] = dep[ma] + 1; for (int i = head[u]; i; i = adj[i].nex) { int v = adj[i].to; if (v == ma) continue; DFS(v, u); siz[u] += siz[v]; if (!hson[u] || siz[v] > siz[hson[u]]) hson[u] = v; } } void DFS2(int u, int t) { dfn[u] = ++ idx; top[u] = t; if (hson[u]) DFS2(hson[u], t); for (int i = head[u]; i; i = adj[i].nex) { int v = adj[i].to; if (v == hson[u] || v == fa[u]) continue; DFS2(v, v); } }
树链剖分(路径求和)
void modify(int x, int y, int v) { while (top[x] != top[y]) { if (dep[top[x]] > dep[top[y]]) swap(x, y); Segt::modify(1, 1, n, dfn[top[y]], dfn[y], v); y = fa[top[y]]; } if (dep[x] > dep[y]) swap(x, y); Segt::modify(1, 1, n, dfn[x], dfn[y], v); } LL query(int x, int y) { LL ret = 0; while (top[x] != top[y]) { if (dep[top[x]] > dep[top[y]]) swap(x, y); (ret += Segt::query(1, 1, n, dfn[top[y]], dfn[y])) %= MOD; y = fa[top[y]]; } if (dep[x] > dep[y]) swap(x, y); (ret += Segt::query(1, 1, n, dfn[x], dfn[y])) %= MOD; return ret; }
主席树
BZOJ3653 [湖南集训]谈笑风生 < DFS序 + 权值主席树>
int dep[MAXN], siz[MAXN], dfn[MAXN], adfn[MAXN], odfn[MAXN], idx; void DFS(int u, int fa) { dep[u] = dep[fa] + 1; siz[u] = 1; adfn[dfn[u] = ++ idx] = u; for (int i = head[u]; i; i = adj[i].nex) { int v= adj[i].to; if (v == fa) continue; DFS(v, u); siz[u] += siz[v]; } odfn[u] = idx; } namespace SEG { const int MAXT = MAXN * 20; // 2 ^ 19 = 5e5; int root[MAXN], tot; int ch[MAXT][2], cnt[MAXT]; LL sum[MAXT]; #define mid ((l + r) >> 1) #define ls (ch[now][0]) #define rs (ch[now][1]) void update(int & now, int pre, int l, int r, int x, int v) { if (!now) now = ++ tot; cnt[now] = cnt[pre] + v; sum[now] = sum[pre] + 1ll * v * x; if (l == r) ls = ch[pre][0], rs = ch[pre][1]; else if (x <= mid) { rs = ch[pre][1]; update(ls, ch[pre][0], l, mid, x, v); } else { ls = ch[pre][0]; update(rs, ch[pre][1], mid + 1, r, x, v); } } void build(int n) { root[0] = 0; for (int i = 1; i <= n; ++ i) update(root[i], root[i - 1], 1, n, dep[adfn[i]], 1); } struct Info { int cnt; LL sum; } ; Info query(int now, int pre, int l, int r, int x, int y) { if (x > y || x > r || y < l) return (Info) { 0, 0 }; if (x <= l && r <= y) return (Info) { cnt[now] - cnt[pre], sum[now] - sum[pre] } ; Info ql = query(ls, ch[pre][0], l, mid, x, y), qr = query(rs, ch[pre][1], mid + 1, r, x, y), ret; ret.cnt = ql.cnt + qr.cnt, ret.sum = ql.sum + qr.sum; return ret; } #undef mid #undef ls #undef rs }
FHQ-Treap
注意: 函数嵌套传参有顺序问题, 稳健为好
动态插点的LIS
const int MAXN = 1e5 + 10; namespace Treap { const int INF = 1e6; const int MAXT = MAXN; // note int root, tot; int ch[MAXT][2], pri[MAXN], siz[MAXN], val[MAXN], mx[MAXN]; #define ls (ch[now][0]) #define rs (ch[now][1]) void pushup(int now) { siz[now] = 1; mx[now] = val[now]; if (ls) siz[now] += siz[ls], mx[now] = max(mx[now], mx[ls]); if (rs) siz[now] += siz[rs], mx[now] = max(mx[now], mx[rs]); } int merge(int x, int y) { // printf("m %d %d\n", x, y); if (!x || !y) return x + y; if (pri[x] < pri[y]) { ch[x][1] = merge(ch[x][1], y); pushup(x); return x; } else { ch[y][0] = merge(x, ch[y][0]); pushup(y); return y; } } void split_s(int now, int & x, int & y, int k) { // printf("split %d %d %d %d\n", now, x, y, k); if (!now) { x = 0, y = 0; return ; } if (siz[ls] >= k) { y = now; split_s(ls, x, ch[y][0], k); } else { x = now; split_s(rs, ch[x][1], y, k - siz[ls] - 1); } pushup(now); } int newnode(int v) { int now = ++ tot; siz[now] = 1; mx[now] = val[now] = v; pri[now] = rand() % 19260817; ch[now][0] = ch[now][1] = 0; return now; } void init() { int x = newnode(1), y = newnode(1); root = merge(x, y); } int getmax(int pos) { int x = -2, y = -2; split_s(root, x, y, pos); int ret = mx[x]; root = merge(x, y); return ret; } void insert(int pos, int v) { int x = -1, y = -1; split_s(root, x, y, pos); int now = newnode(v); // printf("ins %d %d %d\n", x, now,y); root = merge(x, merge(now, y)); } int place(int pos) { int v = getmax(pos) + 1; insert(pos, v); return mx[root]; } void print(int now) { printf("%d : %d %d %d %d [%d, %d]\n" ,now, pri[now], siz[now], val[now], mx[now], ls, rs); } void check() { printf("### %d %d\n", root, tot); for (int i = 1; i <= tot; ++ i) print(i); puts(""); } #undef ls #undef rs }
分治
CDQ分治
BZOJ3295 动态逆序对
对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数。给1到n的一个排列,按照某种顺序依次删
除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数
\(N<=100000 M<=50000\)
Sol;
正序删除就等以倒叙插入
在每个点上记录位置比他前的点和他产生的逆序对数量
对于一个新插入的节点\(A\):
- 如果一个以存在的点\(B\), \(A.pos<B.pos ~\&\&~A.val>B.val\),那么\(A\)对\(B\)产生贡献
- 如果\(A.pos > B.pos~\&\&~A.val < B.val\), 那么\(B\)对\(A\)产生贡献
所以把\((Time, pos, val)\)做三维偏序, 注意左边和右边有双向的贡献
int n, m; struct Fenwick { int val[MAXN]; void upd(int x, int v) { for (int i = x; i <= 100000; i += (i & (-i))) val[i] += v; } int qry(int x) { int ret = 0; for (int i = x; i > 0; i &= (i - 1)) ret += val[i]; return ret; } } T, T2; LL ans, an[MAXN]; int pos[MAXN], ask[MAXN]; struct Node { int t, p, v, id; bool operator < (const Node & b) const { return t < b.t; } } a[MAXN], b[MAXN]; int mark[MAXN]; void merge(int l, int mid, int r) { int i = l, j = mid + 1, k = l; while (k <= r) { if (j > r || (i <= mid && a[i].p <= a[j].p)) { mark[k] = 0; b[k ++] = a[i ++]; } else { mark[k] = 1; b[k ++] = a[j ++]; } } for (int i = l; i <= r; i ++) { if (mark[i] == 0) T.upd(b[i].v, 1); else an[b[i].id] += T.qry(100000) - T.qry(b[i].v); } for (int i = l; i <= r; i ++) if (mark[i] == 0) T.upd(b[i].v, -1); for (int i = r; i >= l; i --) { if (mark[i] == 0) T.upd(b[i].v, 1); else an[b[i].id] += T.qry(b[i].v); } for (int i = l; i <= r; i ++) if (mark[i] == 0) T.upd(b[i].v, -1); for (int i = l; i <= r; i ++) a[i] = b[i]; } void CDQ(int l, int r) { if (l >= r) return ; int mid = (l + r) >> 1; CDQ(l, mid); CDQ(mid + 1, r); merge(l, mid, r); }
整体二分
POJ - 2104 K-th Number
\(1 <= n <= 100 000, 1 <= m <= 5 000\), 给出数组\(a[1..n](\leq1e9)\)
将数组数值和询问一起放入操作序列\(a[]\)中
\(solve(x, y, l, r)\)代表操作编号在\([x,y]\)间的询问的答案在\([l,r]\)之间, 并且当前\([x,y]\)间的询问只能由\([x,y]\)之间的数值贡献
int n, m, tot; struct Fenwick { int val[MAXN]; void upd(int x, int v) { for (int i = x; i <= 100000; i += (i & (-i))) val[i] += v; } int qry(int x) { int ret = 0; for (int i = x; i > 0; i &= (i - 1)) ret += val[i]; return ret; } } T; struct Node { int v, l, r, p, typ, id; } a[MAXN << 1], al[MAXN << 1], ar[MAXN << 1]; int an[MAXN]; void solve(int x, int y, int l, int r) { if (l > r || x >= y) return ; if (l == r) { for (int i = x; i <= y; i ++) if (a[i].typ == 1) an[a[i].id] = l; return; } int mid = (l + r) >> 1; int cntl = 0, cntr = 0; for (int i = x; i <= y; i ++) { if (a[i].typ == 0) { if (a[i].v <= mid) al[++cntl] = a[i], T.upd(a[i].id, 1); else ar[++cntr] = a[i]; } else { int val = T.qry(a[i].r) - T.qry(a[i].l - 1); if (a[i].p <= val) al[++cntl] = a[i]; else a[i].p -= val, ar[++cntr] = a[i]; } } for (int i = x; i <= y; i ++) if (a[i].typ == 0 && a[i].v <= mid) T.upd(a[i].id, -1); for (int i = 1; i <= cntl; i ++) a[x + i - 1] = al[i]; for (int i = 1; i <= cntr; i ++) a[x + cntl + i - 1] = ar[i]; solve(x, x + cntl - 1, l, mid); solve(y - cntr + 1, y, mid + 1, r); } int main() { n = in(); m = in(); for (int i = 1; i <= n; i ++) { a[++ tot].v = in(); a[tot].typ = 0; a[tot].id = i; } for (int i = 1; i <= m; i ++) { a[++ tot] = (Node) {0, in(), in(), in(), 1, i }; } solve(1, tot, -INF, INF); for (int i = 1; i <= m; i ++) printf("%d\n", an[i]); return 0; }
点分治
BZOJ4568: [Scoi2016]幸运数字
题意:
给一颗\(\le 20000\)的树, 有点权, 有\(\le 200000\)的询问, 求两点之间路径上的最大异或和(任选一些数)
Sol:
线性基的部分就略过了
离线询问, 在每个节点上vector存询问(对面的点, 询问编号), 每次处理经过淀粉中心(即LCA为淀粉中心)的询问
注意每个点会被扫到\(\lg\)次, 询问也同理, 而非\(n\)次
所以这样就可以\(O(n \log n \cdot 60 + q \log n + q \cdot 60 ^ 2)\)
注意被扫到\(lg\)次, 但每个询问只有一次有效的处理, 其他只是扫扫而已
int n, m; LL a[MAXN]; struct Linb { LL p[62]; Linb () { memset(p, 0, sizeof p); } void clear() { memset(p, 0, sizeof p); } bool ins(LL x) { for (int i = 60; i >= 0; -- i) { if (!(x & (1LL << i))) continue; if (p[i]) x ^= p[i]; else { p[i] = x; return true; } } return false; } LL getmax() { LL ret = 0; for (int i = 60; i >= 0; -- i) if ((ret ^ p[i]) > ret) ret ^= p[i]; return ret; } } ; Linb unite(Linb a, Linb b) { Linb c = a; for (int i = 60; i >= 0; -- i) if (b.p[i]) c.ins(b.p[i]); return c; } int head[MAXN], nume; struct Adj { int nex, to; } adj[MAXN << 1]; void addedge(int from, int to) { adj[++ nume] = (Adj) { head[from], to }; head[from] = nume; } void link(int u, int v) { addedge(u, v); addedge(v, u); } LL ans[MAXM]; struct Node { int to, id; } ; vector <Node> qry[MAXN]; bool vis[MAXN]; int cen, siz[MAXN], col[MAXN]; Linb lb[MAXN]; void getcen(int u, int fa, int tot) { siz[u] = 1; int mx = 0; for (int i = head[u]; i; i = adj[i].nex) { int v = adj[i].to; if (v == fa || vis[v]) continue; getcen(v, u, tot); mx = max(mx, siz[v]); siz[u] += siz[v]; } mx = max(mx, tot - siz[u]); if (mx <= tot / 2) cen = u; } void calc(int u, int fa) { siz[u] = 1; lb[u] = lb[fa]; lb[u].ins(a[u]); for (int i = 0; i < (int)qry[u].size(); ++ i) { int to = qry[u][i].to; // printf("(%d %d(%d))\n", u, to, col[to]); if (col[to] == cen) ans[qry[u][i].id] = max(ans[qry[u][i].id], unite(lb[to], lb[u]).getmax()); } for (int i = head[u]; i; i = adj[i].nex) { int v = adj[i].to; if (v == fa || vis[v]) continue; calc(v, u); siz[u] += siz[v]; } } void mark(int u, int fa) { col[u] = cen; for (int i = head[u]; i; i = adj[i].nex) { int v = adj[i].to; if (v == fa || vis[v]) continue; mark(v, u); } } void DFS(int u, int tot) { getcen(u, 0, tot); vis[cen] = true; col[cen] = cen; lb[cen].clear(); lb[cen].ins(a[cen]); for (int i = head[cen]; i; i = adj[i].nex) { int v = adj[i].to; if (vis[v]) continue; calc(v, cen); mark(v, cen); } for (int i = head[cen]; i; i = adj[i].nex) { int v = adj[i].to; if (vis[v]) continue; DFS(v, siz[v]); } } int main() { n = in(); m = in(); for (int i = 1; i <= n; ++ i) a[i] = in(); for (int i = 1; i < n; ++ i) link(in(), in()); for (int i = 1; i <= m; ++ i) { int u = in(), v = in(); qry[u].push_back((Node) {v, i}); qry[v].push_back((Node) {u, i}); if (u == v) ans[i] = a[u]; } DFS(1, n); for (int i = 1; i <= m; ++ i) printf("%lld\n", ans[i]); return 0; }
博弈论
SG
SG函数
- 定义
mex
(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。 - 定义 \(SG(now) = mex(SG(nex))\),即对后继状态的SG函数值的集合进行
mex
运算。 - 若看成有向无环图, 那么没有出边的终状态\(SG\)值为\(0\)
SG定理
游戏和的SG函数等于各个游戏SG函数的Nim和(XOR和)
数论
线性筛质数
bool flag[MAXN]; int prm[MAXN], tot; void filter() { for (int i = 2; i <= 1000; ++ i) { if (!flag[i]) prm[++ tot] = i; for (int j = 1; i * prm[j] <= 1000 && j <= tot; ++ j) { flag[i * prm[j]] = true; if (i % prm[j] == 0) break; } } }
欧几里得
LL exgcd(LL a, LL b, LL & x, LL & y) { if (!b) { x = 1, y = 0; return a; } LL d = exgcd(b, a % b, y, x); y -= (a / b) * x; return d; }
\(ax+by=m\)
若\(m \mod \gcd(a, b) \ne 0\)则无解
否则设\(k\cdot \gcd(a, b) = m\)
得到特解\(x_0, y_0\), 显然等式两边同乘\(k\)即可
设这样得到的一组解为\(x_1=x_0\cdot k, y_1=y_0\cdot k\)
同理
得到最小\(x\)整数解, \(x = ((x_1 \mod \frac{b}{\gcd(a,b)})+\frac{b}{\gcd(a,b)}) \mod \frac{b} {\gcd(a,b)}\)
得到最小\(y\)整数解, \(y = ((y_1 \mod \frac{a}{\gcd(a,b)})+\frac{a}{\gcd(a,b)}) \mod \frac{a} {\gcd(a,b)}\)
中国剩余定理
\(m\)不互质
\[\begin{cases} x \equiv a_1(\mod m_1) \\ x \equiv a_2(\mod m_2) \\ \end{cases}\]
得
\[\begin{cases} x = a_1 + k_1\cdot m_1 \\ x = a_2 + k_2\cdot m_2 \\ \end{cases}\]
设\(x_0\)是第一个方程的解, 则通解为\(x=x_0+t\cdot m_1\)
则
\[\begin{aligned}
& x_0+t\cdot m_1=a_2+k_2\cdot m_2 \\
& t\cdot m_1 + k_2 \cdot m_2 = a_2 - x_0 \\
& t\cdot m_1 \equiv a_2 - x_0 (\mod m_2)\\
\end{aligned}\]
- 注意这里\(a_2 - x_0\)不能取模\(m_1\), 因为这样会导致\(t\)不准确(因该是根本没意义), 取模\(m_2\)一方面是\(k\)的取值不关键, 另一方面是为了取正数以及方便扩欧
用exgcd()
求解\(t, k_2\), 则两个方程的解为\(x=x_0 + t \cdot m_1\) 完成了合并
LL mul(LL a, LL b, LL p) { LL ret = 0; while (b) { if (b & 1) b ^= 1, (ret += a) %= p; else b >>= 1, (a += a) %= p; } return ret; } LL exGCD(LL a, LL b, LL & x, LL & y) { if (!b) { x = 1, y = 0; return a; } LL d = exGCD(b, a % b, y, x); y -= (a / b) * x; return d; } LL exCRT() { LL Mo = mo[1], X = a[1]; for (int i = 2; i <= n; ++ i) { LL k1, k2, g = exGCD(Mo, mo[i], k1, k2), newM = Mo / g * mo[i]; LL c = ((a[i] - X % mo[i]) % mo[i] + mo[i]) % mo[i]; if (c % g) return -1; k1 = mul(k1, c / g, mo[i] / g); k1 = (k1 % (mo[i] / g) + mo[i] / g) % (mo[i] / g); k2 = (c - k1 * Mo) / mo[i]; (X += mul(k1, Mo, newM)) %= newM; Mo = newM; } return X; } int main() { n = in(); for (int i = 1; i <= n; ++ i) mo[i] = in(), a[i] = in(); printf("%lld", exCRT()); return 0; }
BSGS及extendBSGS算法
解高次同余方程\(a^x\equiv b(\mod c)\)
考虑\(a^x\)的循环节:
- 若\(\gcd(a,c)=1\), 根据费马小定理得\(a^{c-1}\equiv 1(\mod c)\), 又因为\(a^0\equiv 1(\mod c)\), 所以循环节\(<c\)
- 若不等于\(1\), 等下再说
BSGS
前提: \(\gcd(a,c)=1\)
根据前面分析的循环节取值, 考虑用根号和\(map\)来优化\([0,c)\)的暴力
设\(m=\lceil \sqrt{c}\rceil, x=i*m-j\), 当\(i\in [1,m], j\in [0,m]\)时\(x\)可以取遍\([0,c]\)
\[
a^{im}\equiv ba^j(\mod c)
\]
预先把右边取模后的值, 该值下\(j\)的取值(最大)存进\(map\)
然后暴力左边, 查询\(map\),可以证明越早查到解越小
复杂度\(O(\sqrt{c}*map)\)
map <LL, int> rec; LL qpow(LL a, LL b, LL mod) { LL ret = 1; while (b) { if (b & 1) (ret *= a) %= mod, b ^= 1; else (a *= a) %= mod, b >>= 1; } return ret; } int BSGS(LL a, LL b, LL c) { LL m = ceil(sqrt(c)); LL sum = b, ap = qpow(a, m, c); for (int i = 0; i <= m; ++ i, (sum *= a) %= c) rec[sum] = i; sum = ap; for (int i = 1; i <= m; ++ i, (sum *= ap) %= c) if (rec.count(sum)) return i * m - rec[sum]; return -1; }
exBSGS
若\(d=\gcd(a,c)!=1\)
观察\(a^x=b+kc\)这个等式, 如果\(b\mod d\ne 0\), 那就无解
在有解的情况下, 等式两边可以同除以\(d\)
\[
\frac{a}{d}*a^{x-1}=\frac{b}{d}+k*\frac{c}{d}
\]
不断除以\(d=\gcd(a, \frac{c}{d})\), 直到互质
\[
\frac{a^t}{\prod d}*a^{x-t}=\frac{b}{\prod d}+k*\frac{c}{\prod d}\\
\frac{a^t}{\prod d}*a^{x-t}\equiv\frac{b}{\prod d}(\mod \frac{c}{\prod d})\\
\]
然后就相当于一个带系数的BSGS了, 求解后要加一个\(t\)
而由于求出来的解\(>t\), 所以要暴力求一遍\([0,t]\), 这个\(t\)是辗转相处级别的, 前面提到过, 即\(O(\lg)\)
map <int, int> rec; int gcd(int a, int b) { return !b ? a : gcd(b, a % b); } LL qpow(LL a, LL b, LL mod) { LL ret = 1; while (b) { if (b & 1) (ret *= a) %= mod, b ^= 1; else (a *= a) %= mod, b >>= 1; } return ret; } int exBSGS(LL a, LL b, LL c) { LL d, k = 1; int cnt = 0; for (d = gcd(a, c); d != 1; d = gcd(a, c)) { if (b % d) return -1; (k *= a / d) %= c, ++ cnt; c /= d; b /= d; if (k == b) return cnt; } LL m = ceil(sqrt(c)); LL sum = b, ap = qpow(a, m, c); for (int i = 0; i <= m; ++ i, (sum *= a) %= c) rec[sum] = i; sum = ap * k % c; for (int i = 1; i <= m; ++ i, (sum *= ap) %= c) if (rec.count(sum)) return i * m - rec[sum] + cnt; return -1; }
字符串
KMP
for (int i = 2, j = 0; i <= n; ++ i) { while (a[i] != a[j + 1] && j != 0) j = fail[j]; if (a[i] == a[j + 1] && j + 1 <= n) ++ j; fail[i] = j; //printf("fail[%d] = %d\n", i, fail[i]); } for (int i = 1, j = 0; i <= m; ++ i) { while (b[i] != a[j + 1] && j != 0) j = fail[j]; if (b[i] == a[j + 1] && j + 1 <= n) ++ j; if (j == n) ans ++, j = fail[j]; } printf("%d\n", ans); //aabaabaa
SAM
int size, last; int len[MAXN], link[MAXN], ch[MAXN][30], cnt[MAXN], id[MAXN]; // MAXN = 2 * length(S) void extend(int c) { int cur = ++ size, p = last; len[cur] = len[last] + 1; for (; p != -1 && !ch[p][c]; p = link[p]) ch[p][c] = cur; if (p == -1) link[cur] = 0; else { int q = ch[p][c]; if (len[q] == len[p] + 1) link[cur] = q; else { int clone = ++ size; len[clone] = len[p] + 1; link[clone] = link[q]; memcpy(ch[clone], ch[q], sizeof ch[q]); for (; p != -1 && ch[p][c] == q; p = link[p]) ch[p][c] = clone; link[q] = link[cur] = clone; } } last = cur; } void topo() { for (int i = 0; i <= size; ++ i) ++cnt[len[i]]; for (int i = 1; i <= size; ++ i) cnt[i] += cnt[i - 1]; for (int i = 0; i <= size; ++ i) id[-- cnt[len[i]]] = i; } void build() { link[0] = -1; for (int i = 1; i <= n; ++ i) extend(a[i] - 'a'); topo(); }
注意
- 有依赖的变量之间注意运算先后关系
- 函数嵌套传参有顺序问题, 稳健为好
- MAXT大小, 懒标记优先关系, 下传/更新
- 位运算注意优先级, 尽量加括号
- 结束前检查编译, 文件名, 留 10 分钟