爲後綴計算一個哈希值,知足\(H(i)=H(i+1)x+s[i]\)(其中\(0 \leq i < n, H(n) = 0\)),例如
\[H(4)=s[4]\]
\[H(3)=s[4]x+s[3]\]
通常地,\(H(i)=s[n-1]x^{n-1-i}+s[n-2]x^{n-2-i}+...+s[i+1]x+s[i]\)
對於一段長度爲L的子串s[i]~s[i+L-1],定義他的哈希值\(Hash(i, L) = H(i)-H(i+L)x^L\)。
預處理H和\(x^L\)。注意到hash值很大,咱們把他存在unsigned long long中,這樣就實現了天然溢出,能夠大大減少常數。node
樹狀數組好寫好調,能用樹狀數組的時候儘可能要使用。
樹狀數組從1開始。ios
int lowbit(int x) { return x & -x; } int sum(int x) { int ans = 0; while (x) { ans += bit[x]; x -= lowbit(x); } return ans; } void add(int i, int x) { while (i <= n) { bit[i] += x; i += lowbit(i); } }
假設對於區間\([a, b]\)增長k,令g[i]爲加了k之後的數組。
\[g[i] = s[i] (i < a)\]
\[g[i] = s[i] + i*k - k(a-1) (i >= a 且 i <= b)\]
\[g[i] = s[i] + b*k - k(a-1) (i > b)\]
因此咱們開設兩個樹狀數組。git
直接擴展就能夠了。很是的直觀和顯然。算法
//bzoj3132 void change(int id, int x, int y, int val) { for (int i = x; i <= n; i += i & -i) { for (int j = y; j <= m; j += j & -j) { c[id][i][j] += val; } } } int qu(int id, int x, int y) { int ans = 0; for (int i = x; i > 0; i -= i & -i) { for (int j = y; j > 0; j -= j & -j) { ans += c[id][i][j]; } } return ans; }
這個線段樹版原本自bzoj1798,表明了一種最基礎的線段樹類型,支持區間修改,多重標記下傳等等操做。編程
//bzoj1798 void build(int k, int l, int r) { t[k].l = l; t[k].r = r; if (r == l) { t[k].tag = 1; t[k].add = 0; scanf("%lld", &t[k].sum); t[k].sum %= p; return; } int mid = (l + r) >> 1; build(k << 1, l, mid); build(k << 1 | 1, mid + 1, r); t[k].sum = (t[k << 1].sum + t[k << 1 | 1].sum) % p; t[k].add = 0; t[k].tag = 1; } void pushdown(int k) { if (t[k].add == 0 && t[k].tag == 1) return; ll ad = t[k].add, tag = t[k].tag; ad %= p, tag %= p; int l = t[k].l, r = t[k].r, mid = (l + r) >> 1; t[k << 1].tag = (t[k << 1].tag * tag) % p; t[k << 1].add = ((t[k << 1].add * tag) % p + ad) % p; t[k << 1].sum = (((t[k << 1].sum * tag) % p + (ad * (mid - l + 1) % p)%p)%p) % p; t[k << 1 | 1].tag = (t[k << 1 | 1].tag * tag) % p; t[k << 1 | 1].add = ((t[k << 1 | 1].add * tag) % p + ad) % p; t[k << 1 | 1].sum = (((t[k << 1|1].sum * tag) % p + (ad * (r-mid) % p)%p)%p) % p; t[k].add = 0; t[k].tag = 1; return; } void update(int k) { t[k].sum = (t[k << 1].sum%p + t[k << 1 | 1].sum%p) % p; } void add(int k, int x, int y, ll val) { int l = t[k].l, r = t[k].r, mid = (l + r) >> 1; if (x <= l && r <= y) { t[k].add = (t[k].add + val) % p; t[k].sum = (t[k].sum + (val * (r - l + 1) % p) % p) % p; return; } pushdown(k); if (x <= mid) add(k << 1, x, y, val); if (y > mid) add(k << 1 | 1, x, y, val); update(k); } void mul(int k, int x, int y, ll val) { int l = t[k].l, r = t[k].r, mid = (l + r) >> 1; if (x <= l && r <= y) { t[k].add = (t[k].add * val) % p; t[k].tag = (t[k].tag * val) % p; t[k].sum = (t[k].sum * val) % p; return; } pushdown(k); if (x <= mid) mul(k << 1, x, y, val); if (y > mid) mul(k << 1 | 1, x, y, val); update(k); } ll query(int k, int x, int y) { int l = t[k].l, r = t[k].r, mid = (l + r) >> 1; if (x <= l && r <= y) { return t[k].sum%p; } pushdown(k); ll ans = 0; if (x <= mid) ans = (ans + query(k << 1, x, y)) % p; if (y > mid) ans = (ans + query(k << 1 | 1, x, y)) % p; update(k); return ans%p; }
一種可持久化數據結構。
繼承同樣的節點,只是把修改的從新連接,節省空間。
容易爆空間。
常常與權值線段樹和二分答案結合。數組
int rt[maxn], lc[maxm], rc[maxm], sum[maxm]; void update(int l, int r, int x, int &y, int v) { y = ++sz; sum[y] = sum[x] + 1; if (l == r) return; lc[y] = lc[x]; rc[y] = rc[x]; int mid = (l + r) >> 1; if (v <= mid) update(l, mid, lc[x], lc[y], v); else update(mid + 1, r, rc[x], rc[y], v); }
一種奇怪的姿式,又稱李超線段樹。
給節點打下的標記不進行下傳,而是僅僅在須要的時候進行下傳,這就是所謂永久化標記。
網絡
struct line { double k, b; int id; double getf(int x) { return k * x + b; }; }; bool cmp(line a, line b, int x) { if (!a.id) return 1; return a.getf(x) != b.getf(x) ? a.getf(x) < b.getf(x) : a.id < b.id; } const int maxn = 50010; line t[maxn << 2]; line query(int k, int l, int r, int x) { if (l == r) return t[k]; int mid = (l + r) >> 1; line tmp; if (x <= mid) tmp = query(k << 1, l, mid, x); else tmp = query(k << 1 | 1, mid + 1, r, x); return cmp(t[k], tmp, x) ? tmp : t[k]; } void insert(int k, int l, int r, line x) { if (!t[k].id) t[k] = x; if (cmp(t[k], x, l)) std::swap(t[k], x); if (l == r || t[k].k == x.k) return; int mid = (l + r) >> 1; double X = (t[k].b - x.b) / (x.k - t[k].k); if (X < l || X > r) return; if (X <= mid) insert(k << 1, l, mid, t[k]), t[k] = x; else insert(k << 1 | 1, mid + 1, r, x); } void Insert(int k, int l, int r, int x, int y, line v) { if (x <= l && r <= y) { insert(k, l, r, v); return; } int mid = (l + r) >> 1; if (x <= mid) Insert(k << 1, l, mid, x, y, v); if (y > mid) Insert(k << 1 | 1, mid + 1, r, x, y, v); }
一種最爲經常使用的BBST。數據結構
int ch[maxn][2], fa[maxn]; int size[maxn], data[maxn], sum[maxn], la[maxn], ra[maxn], ma[maxn], cov[maxn], a[maxn]; bool rev[maxn]; int n, m, sz, rt; std::stack<int> st; void update(int x) { if (!x) return; la[x] = std::max(la[l(x)], sum[l(x)] + data[x] + std::max(0, la[r(x)])); ra[x] = std::max(ra[r(x)], sum[r(x)] + data[x] + std::max(0, ra[l(x)])); ma[x] = std::max(std::max(ma[l(x)], ma[r(x)]), data[x] + std::max(0, ra[l(x)]) + std::max(0, la[r(x)])); sum[x] = sum[l(x)] + sum[r(x)] + data[x]; size[x] = size[l(x)] + size[r(x)] + 1; } void reverse(int x) { if (!x) return; std::swap(ch[x][0], ch[x][1]); std::swap(la[x], ra[x]); rev[x] ^= 1; } void recover(int x, int v) { if (!x) return; data[x] = cov[x] = v; sum[x] = size[x] * v; la[x] = ra[x] = ma[x] = std::max(v, sum[x]); } void pushdown(int x) { if (!x) return; if (rev[x]) { reverse(ch[x][0]); reverse(ch[x][1]); rev[x] = 0; } if (cov[x] != -inf) { recover(ch[x][0], cov[x]); recover(ch[x][1], cov[x]); cov[x] = -inf; } } void zig(int x) { int y = fa[x], z = fa[y], l = (ch[y][1] == x), r = l ^ 1; fa[ch[y][l] = ch[x][r]] = y; fa[ch[x][r] = y] = x; fa[x] = z; if (z) ch[z][ch[z][1] == y] = x; update(y); update(x); } void splay(int x, int aim = 0) { for (int y; (y = fa[x]) != aim; zig(x)) if (fa[y] != aim) zig((ch[fa[y]][0] == y) == (ch[y][0] == x) ? y : x); if (aim == 0) rt = x; update(x); } int pick() { if (!st.empty()) { int x = st.top(); st.pop(); return x; } else return ++sz; } int setup(int x) { int t = pick(); data[t] = a[x]; cov[t] = -inf; rev[t] = false; sum[t] = 0; la[t] = ra[t] = ma[t] = -inf; size[t] = 1; return t; } int build(int l, int r) { int mid = (l + r) >> 1, left = 0, right = 0; if (l < mid) left = build(l, mid - 1); int t = setup(mid); if (r > mid) right = build(mid + 1, r); if (left) { ch[t][0] = left, fa[left] = t; } else size[ch[t][0]] = 0; if (right) { ch[t][1] = right, fa[right] = t; } else size[ch[t][1]] = 0; update(t); return t; } int find(int k) { int x = rt, ans; while (x) { pushdown(x); if (k == size[ch[x][0]] + 1) return ans = x; else if (k > size[ch[x][0]] + 1) { k -= size[ch[x][0]] + 1; x = ch[x][1]; } else x = ch[x][0]; } return -1; } void del(int &x) { if (!x) return; st.push(x); fa[x] = 0; del(ch[x][0]); del(ch[x][1]); la[x] = ma[x] = ra[x] = -inf; x = 0; } void print(int x) { if (!x) return; if (ch[x][0]) print(ch[x][0]); std::cout << data[x] << ' '; if (ch[x][1]) print(ch[x][1]); }
您須要寫一種數據結構(可參考題目標題),來維護一個有序數列,其中須要提供如下操做:ide
#include <algorithm> #include <cctype> #include <cstdio> using namespace std; const int maxn = 4e6 + 5; const int inf = 1e9; int ans, n, m, opt, l, r, k, pos, sz, Max; int a[maxn], fa[maxn], ch[maxn][2], size[maxn], cnt[maxn], data[maxn], rt[maxn]; inline int read() { int x = 0, f = 1; char ch = getchar(); while (!isdigit(ch)) { if (ch == '-') f = -1; ch = getchar(); } while (isdigit(ch)) { x = x * 10 + ch - '0'; ch = getchar(); } return x * f; } struct Splay { void clear(int x) { fa[x] = ch[x][0] = ch[x][1] = size[x] = cnt[x] = data[x] = 0; } void update(int x) { if (x) { size[x] = cnt[x]; if (ch[x][0]) size[x] += size[ch[x][0]]; if (ch[x][1]) size[x] += size[ch[x][1]]; } } void zig(int x) { int y = fa[x], z = fa[y], l = (ch[y][1] == x), r = l ^ 1; fa[ch[y][l] = ch[x][r]] = y; fa[ch[x][r] = y] = x; fa[x] = z; if (z) ch[z][ch[z][1] == y] = x; update(y); update(x); } void splay(int i, int x, int aim = 0) { for (int y; (y = fa[x]) != aim; zig(x)) if (fa[y] != aim) zig((ch[fa[y]][0] == y) == (ch[y][0] == x) ? y : x); if (aim == 0) rt[i] = x; } void insert(int i, int v) { int x = rt[i], y = 0; if (x == 0) { rt[i] = x = ++sz; fa[x] = ch[x][0] = ch[x][1] = 0; size[x] = cnt[x] = 1; data[x] = v; return; } while (1) { if (data[x] == v) { cnt[x]++; update(y); splay(i, x); return; } y = x; x = ch[x][v > data[x]]; if (x == 0) { ++sz; fa[sz] = y; ch[sz][0] = ch[sz][1] = 0; size[sz] = cnt[sz] = 1; data[sz] = v; ch[y][v > data[y]] = sz; update(y); splay(i, sz); rt[i] = sz; return; } } } void find(int i, int v) { int x = rt[i]; while (1) { if (data[x] == v) { splay(i, x); return; } else x = ch[x][v > data[x]]; } } int pre(int i) { int x = ch[rt[i]][0]; while (ch[x][1]) x = ch[x][1]; return x; } int succ(int i) { int x = ch[rt[i]][1]; while (ch[x][0]) x = ch[x][0]; return x; } void del(int i) { int x = rt[i]; if (cnt[x] > 1) { cnt[x]--; return; } if (!ch[x][0] && !ch[x][1]) { clear(rt[i]); rt[i] = 0; return; } if (!ch[x][0]) { int oldroot = x; rt[i] = ch[x][1]; fa[rt[i]] = 0; clear(oldroot); return; } if (!ch[x][1]) { int oldroot = x; rt[i] = ch[x][0]; fa[rt[i]] = 0; clear(oldroot); return; } int y = pre(i), oldroot = x; splay(i, y); rt[i] = y; ch[rt[i]][1] = ch[oldroot][1]; fa[ch[oldroot][1]] = rt[i]; clear(oldroot); update(rt[i]); return; } int rank(int i, int v) { int x = rt[i], ans = 0; while (1) { if (!x) return ans; if (data[x] == v) return ((ch[x][0]) ? size[ch[x][0]] : 0) + ans; else if (data[x] < v) { ans += ((ch[x][0]) ? size[ch[x][0]] : 0) + cnt[x]; x = ch[x][1]; } else if (data[x] > v) { x = ch[x][0]; } } } int find_pre(int i, int v) { int x = rt[i]; while (x) { if (data[x] < v) { if (ans < data[x]) ans = data[x]; x = ch[x][1]; } else x = ch[x][0]; } return ans; } int find_succ(int i, int v) { int x = rt[i]; while (x) { if (v < data[x]) { if (ans > data[x]) ans = data[x]; x = ch[x][0]; } else x = ch[x][1]; } return ans; } } sp; void insert(int k, int l, int r, int x, int v) { int mid = (l + r) >> 1; sp.insert(k, v); if (l == r) return; if (x <= mid) insert(k << 1, l, mid, x, v); else insert(k << 1 | 1, mid + 1, r, x, v); } void askrank(int k, int l, int r, int x, int y, int val) { int mid = (l + r) >> 1; if (x <= l && r <= y) { ans += sp.rank(k, val); return; } if (x <= mid) askrank(k << 1, l, mid, x, y, val); if (mid + 1 <= y) askrank(k << 1 | 1, mid + 1, r, x, y, val); } void change(int k, int l, int r, int pos, int val) { int mid = (l + r) >> 1; sp.find(k, a[pos]); sp.del(k); sp.insert(k, val); if (l == r) return; if (pos <= mid) change(k << 1, l, mid, pos, val); else change(k << 1 | 1, mid + 1, r, pos, val); } void askpre(int k, int l, int r, int x, int y, int val) { int mid = (l + r) >> 1; if (x <= l && r <= y) { ans = max(ans, sp.find_pre(k, val)); return; } if (x <= mid) askpre(k << 1, l, mid, x, y, val); if (mid + 1 <= y) askpre(k << 1 | 1, mid + 1, r, x, y, val); } void asksucc(int k, int l, int r, int x, int y, int val) { int mid = (l + r) >> 1; if (x <= l && r <= y) { ans = min(ans, sp.find_succ(k, val)); return; } if (x <= mid) asksucc(k << 1, l, mid, x, y, val); if (mid + 1 <= y) asksucc(k << 1 | 1, mid + 1, r, x, y, val); } int main() { #ifdef D freopen("input", "r", stdin); #endif n = read(), m = read(); for (int i = 1; i <= n; i++) a[i] = read(), Max = max(Max, a[i]), insert(1, 1, n, i, a[i]); for (int i = 1; i <= m; i++) { opt = read(); if (opt == 1) { l = read(), r = read(), k = read(); ans = 0; askrank(1, 1, n, l, r, k); printf("%d\n", ans + 1); } else if (opt == 2) { l = read(), r = read(), k = read(); int head = 0, tail = Max + 1; while (head != tail) { int mid = (head + tail) >> 1; ans = 0; askrank(1, 1, n, l, r, mid); if (ans < k) head = mid + 1; else tail = mid; } printf("%d\n", head - 1); } else if (opt == 3) { pos = read(); k = read(); change(1, 1, n, pos, k); a[pos] = k; Max = std::max(Max, k); } else if (opt == 4) { l = read(); r = read(); k = read(); ans = 0; askpre(1, 1, n, l, r, k); printf("%d\n", ans); } else if (opt == 5) { l = read(); r = read(); k = read(); ans = inf; asksucc(1, 1, n, l, r, k); printf("%d\n", ans); } }
void getroot(int x, int fa) { size[x] = 1; f[x] = 0; for (int i = 0; i < G[x].size(); i++) { edge &e = G[x][i]; if (!vis[e.to] && e.to != fa) { getroot(e.to, x); size[x] += size[e.to]; f[x] = max(f[x], size[e.to]); } } f[x] = max(f[x], sum - size[x]); if (f[x] < f[rt]) rt = x; } void getdeep(int x, int fa) { cnt[deep[x]]++; for (int i = 0; i < G[x].size(); i++) { edge &e = G[x][i]; if (!vis[e.to] && e.to != fa) { deep[e.to] = (deep[x] + e.weigh) % 3; getdeep(e.to, x); } } } int cal(int x, int now) { cnt[0] = cnt[1] = cnt[2] = 0; deep[x] = now; getdeep(x, 0); return cnt[1] * cnt[2] * 2 + cnt[0] * cnt[0]; } void work(int x) { ans += cal(x, 0); //統計不一樣子樹經過重心的個數 vis[x] = 1; #ifndef ONLINE_JUDGE printf("In root %d: %d\n", rt, ans); #endif for (int i = 0; i < G[x].size(); i++) { edge &e = G[x][i]; if (!vis[e.to]) { ans -= cal(e.to, e.weigh); //去除在同一個子樹中被重複統計的 rt = 0; sum = size[e.to]; getroot(e.to, 0); work(rt); // Decrease and Conquer } } }
void dfs1(int x) { vis[x] = size[x] = 1; for (int i = 1; i <= 17; i++) { if (deep[x] < (1 << i)) break; fa[x][i] = fa[fa[x][i - 1]][i - 1]; } for (int i = head[x]; i; i = e[i].next) { if (!vis[e[i].to]) { deep[e[i].to] = deep[x] + 1; fa[e[i].to][0] = x; dfs1(e[i].to); size[x] += size[e[i].to]; } } } void dfs2(int x, int chain) { pl[x] = ++sz; que[sz] = x; belong[x] = chain; int k = 0; for (int i = head[x]; i; i = e[i].next) if (deep[e[i].to] > deep[x] && size[k] < size[e[i].to]) k = e[i].to; if (!k) return; dfs2(k, chain); for (int i = head[x]; i; i = e[i].next) if (e[i].to != k && deep[e[i].to] > deep[x]) dfs2(e[i].to, e[i].to); } void update(int k) {} void build(int k, int l, int r) { t[k].l = l, t[k].r = r, t[k].s = 1, t[k].tag = -1; if (l == r) { t[k].lc = t[k].rc = value[que[l]]; return; } int mid = (l + r) >> 1; build(k << 1, l, mid); build(k << 1 | 1, mid + 1, r); update(k); } int lca(int x, int y) { if (deep[x] < deep[y]) std::swap(x, y); int t = deep[x] - deep[y]; for (int i = 0; i <= 17; i++) { if (t & (1 << i)) x = fa[x][i]; } for (int i = 17; i >= 0; i--) { if (fa[x][i] != fa[y][i]) { x = fa[x][i]; y = fa[y][i]; } } if (x == y) return x; else return fa[x][0]; } void pushdown(int k) {} int query(int k, int x, int y) {}//線段樹操做 int getcolor(int k, int pos) {}//線段樹操做 int solvesum(int x, int f) { int sum = 0; while (belong[x] != belong[f]) { sum += query(1, pl[belong[x]], pl[x]); if (getcolor(1, pl[belong[x]]) == getcolor(1, pl[fa[belong[x]][0]])) sum--; x = fa[belong[x]][0]; } sum += query(1, pl[f], pl[x]); return sum; } void change(int k, int x, int y, int c) {}//線段樹操做 void solvechange(int x, int f, int c) { while (belong[x] != belong[f]) { change(1, pl[belong[x]], pl[x], c); x = fa[belong[x]][0]; } change(1, pl[f], pl[x], c); } void solve() { dfs1(1); dfs2(1, 1); build(1, 1, n); for (int i = 1; i <= m; i++) { char ch[10]; scanf("%s", ch); if (ch[0] == 'Q') { int a, b; scanf("%d %d", &a, &b); int t = lca(a, b); printf("%d\n", solvesum(a, t) + solvesum(b, t) - 1); } else { int a, b, c; scanf("%d %d %d", &a, &b, &c); int t = lca(a, b); solvechange(a, t, c); solvechange(b, t, c); } } }
對於樹上的操做,咱們如今已經有了樹鏈剖分能夠處理這些問題。然而樹鏈剖分不支持動態維護樹上的拓撲結構。因此咱們須要Link-Cut Tree(lct)來解決這種動態樹問題。順帶一提的是,動態樹也是Tarjan發明的。函數
首先咱們介紹一個概念:Preferred path(實邊),其餘的邊都是虛邊。咱們使用splay來實時地維護這條路徑。
lct的核心操做是access。access操做能夠把虛邊變爲實邊,經過改變splay的拓撲結構來維護實邊。
有了這個數據結構,咱們依次來考慮兩個操做。
對於連接兩個節點,咱們須要首先把x節點變爲他所在樹的根節點,而後直接令fa[x] = y便可。
怎樣換根呢?稍微思考一下能夠發現,咱們直接把從根到他的路徑反轉便可。
對於第二種操做,咱們直接斷開拓撲關係便可。
另外實現的時候要注意,splay的根節點的父親是他的上一個節點。因此zig和splay的寫法應該格外注意。
inline bool isroot(int x) { return ch[fa[x]][0] != x && ch[fa[x]][1] != x; } void pushdown(int k) { if (rev[k]) { rev[k] = 0; rev[ch[k][0]] ^= 1; rev[ch[k][1]] ^= 1; std::swap(ch[k][0], ch[k][1]); } } void zig(int x) { int y = fa[x], z = fa[y], l = (ch[y][1] == x), r = l ^ 1; if (!isroot(y)) ch[z][ch[z][1] == y] = x; fa[ch[y][l] = ch[x][r]] = y; fa[ch[x][r] = y] = x; fa[x] = z; } void splay(int x) { stack<int> st; st.push(x); for (int i = x; !isroot(i); i = fa[i]) st.push(fa[i]); while (!st.empty()) { pushdown(st.top()); st.pop(); } for (int y = fa[x]; !isroot(x); zig(x), y = fa[x]) if (!isroot(y)) zig((ch[fa[y]][0] == y) == (ch[y][0] == x) ? y : x); } void access(int x) { int t = 0; while (x) { splay(x); ch[x][1] = t; t = x; x = fa[x]; } } void rever(int x) { access(x); splay(x); rev[x] ^= 1; } void link(int x, int y) { rever(x); fa[x] = y; splay(x); } void cut(int x, int y) { rever(x); access(y); splay(y); ch[y][0] = fa[x] = 0; } int find(int x) { access(x); splay(x); int y = x; while (ch[y][0]) y = ch[y][0]; return y; }
對於字符串S的前i個字符構成的子串,既是它的後綴又是它的前綴的字符串中(它自己除外),最長的長度記做next[i]
多用於DP
for (int i = 2; i <= m; i++) { while (j > 0 && ch[j + 1] != ch[i]) j = p[j]; if (ch[j + 1] == ch[i]) j++; p[i] = j; }
KMP在Trie上的擴展.
void insert(char s[101]) { int now = 1, c; for (int i = 0; i < strlen(s); i++) { c = s[i] - 'A' + 1; if (a[now][c]) now = a[now][c]; else now = a[now][c] = ++sz; } leaf[now] = 1; } void ac() { std::queue<int> q; q.push(1); point[1] = 0; while (!q.empty()) { int u = q.front(); q.pop(); for (int i = 1; i <= 26; i++) { if (!a[u][i]) continue; int k = point[u]; while (!a[k][i]) k = point[k]; point[a[u][i]] = a[k][i]; if (leaf[a[k][i]]) leaf[a[u][i]] = 1; q.push(a[u][i]); } } }
void getsa(int sa[maxn], int rank[maxn], int Sa[maxn], int Rank[maxn]) { for (int i = 1; i <= n; i++) v[rank[sa[i]]] = i; for (int i = n; i >= 1; i--) if (sa[i] > k) Sa[v[rank[sa[i] - k]]--] = sa[i] - k; for (int i = n - k + 1; i <= n; i++) Sa[v[rank[i]]--] = i; for (int i = 1; i <= n; i++) Rank[Sa[i]] = Rank[Sa[i - 1]] + (rank[Sa[i - 1]] != rank[Sa[i]] || rank[Sa[i - 1] + k] != rank[Sa[i] + k]); } void getheight(int sa[maxn], int rank[maxn]) { int i, k = 0; for (i = 1; i <= n; height[rank[i++]] = k) { if (k) k--; int j = sa[rank[i] - 1]; while (a[i + k] == a[j + k]) k++; } } void da() { p = 0, q = 1, k = 1; for (int i = 1; i <= n; i++) v[a[i]]++; for (int i = 1; i <= 2; i++) v[i] += v[i - 1]; for (int i = 1; i <= n; i++) sa[p][v[a[i]]--] = i; for (int i = 1; i <= n; i++) rank[p][sa[p][i]] = rank[p][sa[p][i - 1]] + (a[sa[p][i - 1]] != a[sa[p][i]]); while (k < n) { getsa(sa[p], rank[p], sa[q], rank[q]); p ^= 1; q ^= 1; k <<= 1; } getheight(sa[p], rank[p]); }
struct Suffix_Automaton { ll fa[maxn], trans[maxn][26], len[maxn], right[maxn]; ll last, root, sz; bool flag[maxn]; void init() { memset(flag, 0, sizeof(flag)); sz = 0; last = root = ++sz; } void insert(ll x) { ll p = last, np = last = ++sz; len[np] = len[p] + 1; flag[np] = 1; right[np] = right[p] + 1; for (; !trans[p][x]; p = fa[p]) trans[p][x] = np; if (p == 0) fa[np] = root; else { ll q = trans[p][x]; if (len[q] == len[p] + 1) { fa[np] = q; } else { ll nq = ++sz; fa[nq] = fa[q]; memcpy(trans[nq], trans[q], sizeof(trans[q])); len[nq] = len[p] + 1; fa[q] = fa[np] = nq; for (; trans[p][x] == q; p = fa[p]) trans[p][x] = nq; } } } } sam;
void manacher() { int mx = 1, id = 1; for (int i = n; i; i--) str[i * 2] = '#', str[i * 2 - 1] = str[i]; n <<= 1; for (int i = 1; i <= n; i++) { p[i] = std::min(p[id * 2 - i], mx - i); while (i - p[i] > 0 && str[i - p[i]] == str[i + p[i]]) { int al = (i - p[i]) / 2 + 1; int ar = (i + p[i] + 1) / 2; // printf("%d %d\n", al, ar); sam.query(al, ar); p[i]++; } if (i + p[i] > mx) mx = i + p[i], id = i; } }
隨便給一個例題吧.
給定一個數列,您須要支持一下兩種操做:1.給[l,r]同加一個數. 2.詢問[l,r]中有多少數字大於或等於v.
把數據分爲\(\sqrt n\)一份,那麼對於每個查詢,咱們均可以把這個查詢分爲\(\sqrt n\)個區間,修改的時候也是\(O(\sqrt n)\)的級別,因此總的複雜度就是\(O(\sqrt n log \sqrt n)\)
具體地,對於每一塊,咱們都存儲排序前和排序後的序列,這樣咱們就解決了這個題。
#include <algorithm> #include <cctype> #include <cmath> #include <cstdio> int n, q, m, block; const int maxn = 1000001; int a[maxn], b[maxn], pos[maxn], add[maxn]; using std::sort; using std::min; inline int read() {} inline void reset(int x) { int l = (x - 1) * block + 1, r = min(x * block, n); for (int i = l; i <= r; i++) b[i] = a[i]; sort(b + l, b + r + 1); } inline int find(int x, int v) { int l = (x - 1) * block + 1, r = min(x * block, n); int last = r; while (l <= r) { int mid = (l + r) >> 1; if (b[mid] < v) l = mid + 1; else r = mid - 1; } return last - l + 1; } inline void update(int x, int y, int v) { if (pos[x] == pos[y]) { for (int i = x; i <= y; i++) a[i] = a[i] + v; } else { for (int i = x; i <= pos[x] * block; i++) a[i] = a[i] + v; for (int i = (pos[y] - 1) * block + 1; i <= y; i++) a[i] = a[i] + v; } reset(pos[x]); reset(pos[y]); for (int i = pos[x] + 1; i < pos[y]; i++) add[i] += v; } inline int query(int x, int y, int v) { int sum = 0; if (pos[x] == pos[y]) { for (int i = x; i <= y; i++) if (a[i] + add[pos[i]] >= v) sum++; } else { for (int i = x; i <= pos[x] * block; i++) if (a[i] + add[pos[i]] >= v) sum++; for (int i = (pos[y] - 1) * block + 1; i <= y; i++) if (a[i] + add[pos[i]] >= v) sum++; for (int i = pos[x] + 1; i < pos[y]; i++) sum += find(i, v - add[i]); } return sum; } int main() { #ifndef ONLINE_JUDGE freopen("input", "r", stdin); #endif n = read(), q = read(); if (n >= 500000) block = 3676; else if (n >= 5000) { block = 209; } else block = int(sqrt(n)); for (int i = 1; i <= n; i++) { a[i] = read(); pos[i] = (i - 1) / block + 1; b[i] = a[i]; } if (n % block) m = n / block + 1; else m = n / block; for (int i = 1; i <= m; i++) reset(i); for (int i = 1; i <= q; i++) { char ch[5]; int x, y, v; scanf("%s", ch); x = read(), y = read(), v = read(); if (ch[0] == 'M') update(x, y, v); else printf("%d\n", query(x, y, v)); } }
若是你知道了[L,R]的答案。你能夠在O(1)的時間下獲得[L,R-1]和[L,R+1]和[L-1,R]和[L+1,R]的答案的話。就可使用莫隊算法。
對於莫隊算法我感受就是暴力。只是預先知道了全部的詢問。能夠合理的組織計算每一個詢問的順序以此來下降複雜度。要知道咱們算完[L,R]的答案後如今要算[L',R']的答案。因爲能夠在O(1)的時間下獲得[L,R-1]和[L,R+1]和[L-1,R]和[L+1,R]的答案.因此計算[L',R']的答案花的時間爲|L-L'|+|R-R'|。若是把詢問[L,R]看作平面上的點a(L,R).詢問[L',R']看作點b(L',R')的話。那麼時間開銷就爲兩點的曼哈頓距離。因此對於每一個詢問看作一個點。咱們要按必定順序計算每一個值。那開銷就爲曼哈頓距離的和。要計算到每一個點。那麼路徑至少是一棵樹。因此問題就變成了求二維平面的最小曼哈頓距離生成樹。
關於二維平面最小曼哈頓距離生成樹。感興趣的能夠參考胡澤聰大佬的這篇文章
這樣只要順着樹邊計算一次就ok了。能夠證實時間複雜度爲\(O(n∗\sqrt n)\).
可是這種方法編程複雜度稍微高了一點。因此有一個比較優雅的替代品。那就是先對序列分塊。而後對於全部詢問按照L所在塊的大小排序。若是同樣再按照R排序。而後按照排序後的順序計算。爲何這樣計算就能夠下降複雜度呢。
1、i與i+1在同一塊內,r單調遞增,因此r是O(n)的。因爲有n^0.5塊,因此這一部分時間複雜度是n^1.5。
2、i與i+1跨越一塊,r最多變化n,因爲有n^0.5塊,因此這一部分時間複雜度是n^1.5
3、i與i+1在同一塊內時變化不超過n^0.5,跨越一塊也不會超過2*n^0.5,不妨看做是n^0.5。因爲有n個數,因此時間複雜度是n^1.5
因而就變成了Θ(n1.5)
#include <algorithm> #include <cmath> #include <cstdio> #include <cstring> #include <iostream> const int maxn = 50010; #define ll long long ll num[maxn], up[maxn], dw[maxn], ans, aa, bb, cc; int col[maxn], pos[maxn]; struct qnode { int l, r, id; } qu[maxn]; bool cmp(qnode a, qnode b) { if (pos[a.l] == pos[b.l]) return a.r < b.r; else return pos[a.l] < pos[b.l]; } ll gcd(ll x, ll y) { ll tp; while ((tp = x % y)) { x = y; y = tp; } return y; } void update(int x, int d) { ans -= num[col[x]] * num[col[x]]; num[col[x]] += d; ans += num[col[x]] * num[col[x]]; } int main() { int n, m, bk, pl, pr, id; #ifndef ONLINE_JUDGE freopen("input", "r", stdin); #endif scanf("%d %d", &n, &m); memset(num, 0, sizeof(num)); bk = ceil(sqrt(1.0 * n)); for (int i = 1; i <= n; i++) { scanf("%d", &col[i]); pos[i] = (i - 1) / bk; } for (int i = 0; i < m; i++) { scanf("%d %d", &qu[i].l, &qu[i].r); qu[i].id = i; } std::sort(qu, qu + m, cmp); pl = 1, pr = 0; ans = 0; for (int i = 0; i < m; i++) { id = qu[i].id; if (qu[i].l == qu[i].r) { up[id] = 0, dw[id] = 1; continue; } if (pr < qu[i].r) { for (int j = pr + 1; j <= qu[i].r; j++) update(j, 1); } else { for (int j = pr; j > qu[i].r; j--) update(j, -1); } pr = qu[i].r; if (pl < qu[i].l) { for (int j = pl; j < qu[i].l; j++) update(j, -1); } else { for (int j = pl - 1; j >= qu[i].l; j--) update(j, 1); } pl = qu[i].l; aa = ans - qu[i].r + qu[i].l - 1; bb = (ll)(qu[i].r - qu[i].l + 1) * (qu[i].r - qu[i].l); cc = gcd(aa, bb); aa /= cc, bb /= cc; up[id] = aa, dw[id] = bb; } for (int i = 0; i < m; i++) printf("%lld/%lld\n", up[i], dw[i]); }
### 1.16 總體二分&CDQ分治
stack<Edge> S; int dfs(int u, int fa) { int lowu = pre[u] = ++dfs_clock; int child = 0; for(int i = 0; i < G[u].size(); i++) { int v = G[u][i]; Edge e = (Edge){u, v}; if(!pre[v]) { S.push(e); child++; int lowv = dfs(v, u); lowu = min(lowu, lowv); if(lowv >= pre[u]) { iscut[u] = true; bcc_cnt++; bcc[bcc_cnt].clear(); for(;;) { Edge x = S.top(); S.pop(); if(bccno[x.u] != bcc_cnt) {bcc[bcc_cnt].push_back(x.u); bccno[x.u] = bcc_cnt;} if(bccno[x.v] != bcc_cnt) {bcc[bcc_cnt].push_back(x.v); bccno[x.v] = bcc_cnt;} if(x.u == u && x.v == v) break; } } } else if(pre[v] < pre[u] && v != fa) { S.push(e); lowu = min(lowu, pre[v]); } } if(fa < 0 && child == 1) iscut[u] = 0; return lowu; }
kosaraju算法.
void dfs(int v) { vis[v] = true; for (int i = 0; i < G[v].size(); i++) { if (!vis[G[v][i]]) dfs(G[v][i]); } vs.push_back(v); } void rdfs(int v, int k) { vis[v] = true; cnt[v] = k; for (int i = 0; i < rG[v].size(); i++) { if (!vis[rG[v][i]]) rdfs(rG[v][i], k); } vs.push_back(v); sc[k].push_back(v); } int scc() { memset(vis, 0, sizeof(vis)); vs.clear(); for (int v = 1; v <= n; v++) { if (!vis[v]) dfs(v); } memset(vis, 0, sizeof(vis)); int k = 0; for (int i = vs.size() - 1; i >= 0; i--) { if (!vis[vs[i]]) { rdfs(vs[i], k++); } } return k; }
雖然是NOIp(professional)知識, 可是因爲在省選中很是經常使用, 仍是寫一下.
最短路算法也會在分層圖中考察.
spfa也能夠運用在DP中的轉移.
void spfa() { memset(dist, 0x3f, sizeof(dist)); dist[s][0] = 0; queue<state> q; q.push((state){s, 0}); memset(inq, 0, sizeof(inq)); inq[s][0] = 1; while (!q.empty()) { state u = q.front(); q.pop(); inq[u.pos][u.k] = 0; for (int i = 0; i < G[u.pos].size(); i++) { edge &e = G[u.pos][i]; if (dist[e.to][u.k] > dist[u.pos][u.k] + e.value) { dist[e.to][u.k] = dist[u.pos][u.k] + e.value; if (!inq[e.to][u.k]) { q.push((state){e.to, u.k}); inq[e.to][u.k] = 1; } } if (u.k < k && dist[e.to][u.k + 1] > dist[u.pos][u.k]) { dist[e.to][u.k + 1] = dist[u.pos][u.k]; if (!inq[e.to][u.k + 1]) { q.push((state){e.to, u.k + 1}); inq[e.to][u.k + 1] = 1; } } } } }
spfa能夠用來判負環. 所謂負環就是環上邊權和爲負的環. 通常使用dfs版本spfa判負環.
double dist[maxn]; inline void spfa(int x) { int i; vis[x] = false; for (i = 0; i < rg[x].size(); i++) { edge &e = rg[x][i]; if (dist[e.to] > dist[x] + e.value) if (!vis[e.to]) { flag = true; break; } else { dist[e.to] = dist[x] + e.value; spfa(e.to); } } vis[x] = true; } bool check(double lambda) { for (int i = 1; i <= n; i++) { rg[i].clear(); for (int j = 0; j < G[i].size(); j++) { rg[i].push_back((edge){G[i][j].to, (double)G[i][j].value - lambda}); } } memset(vis, 1, sizeof(vis)); memset(dist, 0, sizeof(dist)); flag = false; for (int i = 1; i <= n; i++) { spfa(i); if (flag) return true; } return false; }
最小生成樹算是很常見的考點.
關於最小生成樹, 咱們有如下結論:
歐幾里德最小生成樹
動態最小生成樹: 使用Link-Cut Tree維護.
矩陣樹定理(Matrix-Tree)
下面咱們介紹一種新的方法——Matrix-Tree定理(Kirchhoff矩陣-樹定理)。Matrix-Tree定理是解決生成樹計數問題最有力的武器之一。它首先於1847年被Kirchhoff證實。在介紹定理以前,咱們首先明確幾個概念:
一、G的度數矩陣\(D[G]\)是一個\(n \times n\)的矩陣,而且知足:當\(i\not = j\)時,\(d_{ij}=0\);當\(i=j\)時,\(d_{ij}\)等於\(v_i\)的度數。
二、G的鄰接矩陣\(A[G]\)也是一個\(n \times n\)的矩陣, 而且知足:若是\(v_i\)、\(v_j\)之間有邊直接相連,則\(a_{ij}\)=1,不然爲0。
咱們定義\(G\)的Kirchhoff矩陣(也稱爲拉普拉斯算子)C[G]爲C[G]=D[G]-A[G],則Matrix-Tree定理能夠描述爲:G的全部不一樣的生成樹的個數等於其Kirchhoff矩陣C[G]任何一個n-1階主子式的行列式的絕對值。所謂n-1階主子式,就是對於r(1≤r≤n),將C[G]的第r行、第r列同時去掉後獲得的新矩陣,用Cr[G]表示。
kruskal 算法: 貪心地選取每一條邊
首先咱們假設讀者已經有了線性規劃的基本知識.
最大流問題的線性規劃描述:
$$
\begin{alignat}{2}
\max\quad &f_{ts} &{}& \tag{LP1} \label{eqn - lp}\
\mbox{s.t.}\quad
&f_{u,v}\leqslant c_{u,v}, &\quad& (u,v)\in E\
&\sum_{v} f_{uv} = \sum_v f_{vu}, &{}& u \in V\
& f_{uv} \geqslant 0, &{}& (u,v) \in E \cup{(t,s)}
\end{alignat}
\[ 最小割問題的線性規劃描述: \]
\begin{alignat}{2}
\min\quad &\sum_{(u,v) \in E}c_{uv}d_{uv} &{}& \tag{LP2} \
\mbox{s.t.}\quad
&d_{u,v}-p_u+p_v &\geqslant 0, &\quad& (u,v)\in E\
&p_s-p_t &\geqslant 1\
&p_u, d_{uv} \in {0, 1}
\end{alignat}
$$
令\(p_u=[u \in S]\), \(d_{uv}=\max\{p_u-p_v, 0\}\).
考慮最大流的對偶: 記由容量限制產生的變量爲\(d_{uv}\), 由點\(u\)的流量守恆產生的變量爲\(p_u\), 那麼對偶問題就是:
$$
\begin{alignat}{2}
\min\quad &\sum_{(u,v) \in E}c_{uv}d_{uv} &{}& \tag{LP3} \
\mbox{s.t.}\quad
&d_{u,v}-p_u+p_v &\geqslant 0, &\quad& (u,v)\in E\
&p_s-p_t &\geqslant 1\
&d_{uv} &\geqslant 0, &{}&(u,v)\in E
\end{alignat}
$$
咱們得出結論: (最大流最小割定理)給定一個源爲\(s\), 匯爲\(t\)的網絡, 則\(s,t\)的最大流等於\(s,t\)的最小割.
int dist[maxn], iter[maxn]; inline void bfs(int s) { memset(dist, -1, sizeof(dist)); dist[s] = 0; queue<int> q; q.push(s); while (!q.empty()) { int u = q.front(); q.pop(); for (int i = 0; i < G[u].size(); i++) { edge &e = edges[G[u][i]]; if (e.cap > 0 && dist[e.to] == -1) { dist[e.to] = dist[u] + 1; q.push(e.to); } } } } inline int dfs(int s, int t, int flow) { if (s == t) return flow; for (int &i = iter[s]; i < G[s].size(); i++) { edge &e = edges[G[s][i]]; if (e.cap > 0 && dist[e.to] > dist[s]) { int d = dfs(e.to, t, min(e.cap, flow)); if (d > 0) { e.cap -= d; edges[G[s][i] ^ 1].cap += d; return d; } } } return 0; } inline int dinic(int s, int t) { int flow = 0; while (1) { bfs(s); if (dist[t] == -1) return flow; memset(iter, 0, sizeof(iter)); int d; while (d = dfs(s, t, inf)) flow += d; } return flow; }
泛指一種與費用相關的流算法.EK算法比較經常使用
bool spfa(ll &flow, ll &cost) { for (int i = 0; i <= n + 1; i++) { dist[i] = -inf; } memset(inq, 0, sizeof(inq)); dist[s] = 0, inq[s] = 1, pre[s] = 0, fi[s] = inf; queue<int> q; q.push(s); while (!q.empty()) { int u = q.front(); q.pop(); inq[u] = 0; for (int i = 0; i < G[u].size(); i++) { edge &e = E[G[u][i]]; if (e.cap > e.flow && dist[e.to] < dist[u] + e.cost) { dist[e.to] = dist[u] + e.cost; pre[e.to] = G[u][i]; fi[e.to] = min(fi[u], e.cap - e.flow); if (!inq[e.to]) { q.push(e.to); inq[e.to] = 1; } } } } if (dist[t] <= -inf) return false; if (cost + dist[t] * fi[t] < 0) { ll temp = cost / (-dist[t]); //temp:還可以增長的流 flow += temp; return false; } flow += fi[t]; cost += dist[t] * fi[t]; int u = t; while (u != s) { E[pre[u]].flow += fi[t]; E[pre[u] ^ 1].flow -= fi[t]; u = E[pre[u]].from; } return true; } ll mcmf(int s, int t) { ll flow = 0; ll cost = 0; while (spfa(flow, cost)) ; return flow; }
for (int i = 1; i <= n; i++) { int now = cc[i].id; int u = now, v = now + n; bfs(u); if (dist[v] != -1) continue; rec[tot++] = now; dinic(t, v); dinic(u, s); edges[(now - 1) * 2].cap = edges[(now - 1) * 2 + 1].cap = 0; }
對於不存在孤立點的圖,|最大匹配|+|最小邊覆蓋| = |V|
最大獨立集合 + 最小頂點覆蓋 = V
對於二分圖:|最大匹配| = |最小頂點覆蓋|
平面圖的頂點個數、邊數和麪的個數之間有一個以歐拉命名的公式:
其中,V是頂點的數目,E是邊的數目,F是面的數目,C是組成圖形的連通部分的數目。當圖是單連通圖的時候,公式簡化為:
任何一個平面圖的對偶圖仍然是平面圖
首先咱們有歐幾里德算法:
\[gcd(a, b) = gcd(a\ mod\ b, b)\]
擴展歐幾里德算法解決了這樣的問題:
\[ ax + by = gcd(a,b)\]
咱們先考察一種特殊的狀況:
當\(b=0\)時,咱們直接能夠有解:
\[ \begin{eqnarray} \left\{ \begin{array}{lll} x = 1 \\ y = 0 \end{array} \right. \end{eqnarray} \]
通常地,咱們令\(c = a\ mod \ b\),遞歸地解下面的方程:
\[bx^{'}+cy^{'}=gcd(b,c)\]
根據歐幾里德算法,有
\[bx^{'}+cy^{'}=gcd(a,b)\]
根據\(mod\)的定義咱們能夠有
\[c = a - b\lfloor\frac{a}{b}\rfloor\]
帶入原式
\[bx^{'}+(a - b\lfloor\frac{a}{b}\rfloor)y^{'}=gcd(a,b)\]
爲了體現與\(a,b\)的關係
\[ay^{'}+b(x^{'}-\lfloor\frac{a}{b}\rfloor y^{'})=gcd(a,b)\]
因此這樣就完成了回溯。
這個算法的思想體如今了下面的程序裏。
void gcd(int a, int b, int &d, int &x, int &y) { if(!b) {d = a; x = 1; y = 0; } else { gcd(b, a%b, d, y, x); y -= x * (a/b); } }
首先給出線性篩素數的程序。
void get_su(int n) { tot = 0; for(int i = 2; i <= n; i++) { if(!check[i]) prime[tot++] = i; for(int j = 0; j < tot; j++) { if(i * prime[j] > n) break; check[i * prime[j]] = true; if(i % prime[j] == 0) break; } } }
能夠證實的是,每一個合數都僅僅會被他的最小質因數篩去,這段代碼的時間複雜度是\(\Theta (n)\)的,也就是所謂線性篩。
證實:設合數\(n\)最小的質因數爲\(p\),它的另外一個大於\(p\)的質因數爲\(p^{'}\),另\(n = pm=p^{'}m^{'}\)。 觀察上面的程序片斷,能夠發現\(j\)循環到質因數\(p\)時合數n第一次被標記(若循環到\(p\)以前已經跳出循環,說明\(n\)有更小的質因數),若也被\(p^{'}\)標記,則是在這以前(由於\(m^{'}<m\)),考慮\(i\)循環到\(m^{'}\),注意到\(n=pm=p^{'}m^{'}\)且\(p,p^{'}\)爲不一樣的質數,所以\(p|m^{'}\),因此當j循環到質數p後結束,不會循環到\(p^{'}\),這就說明\(n\)不會被\(p^{'}\)篩去。
\[f(ab) = f(a)f(b)\],則函數f被稱爲積性函數。
容易看出,對於任意積性函數,\(f(1)=1\)。
\[f(N)=f(\prod p_i^{a_i})=\prod f(p_i^{a_i})\],若是\(f\)還知足徹底積性,那麼
\[f(N)=\prod f(p_i)^{a_i}\]
\[n^{\varphi(m)}\equiv 1 \pmod m\ \ \ \ n\perp m\]
可使用這個定理計算逆元。
\[\varphi(p^k) = p^k-p^{k-1}=(p-1)p^{k-1}\]
\[\varphi(m) = \prod_{p|m}(p^{m_p}-p^{m_p-1}) = m \prod_{p|m}(1-\frac{1}{p})=\prod(p-1)p^{m_p-1}\]
例如歐拉函數\(\varphi\),\(\varphi(p^k)=(p-1)p^{k-1}\),所以進行篩法求\(\varphi(p*m)\)時,若是\(p|m\),那麼\(p*m\)中\(p\)的次數不爲1,因此咱們能夠從\(m\)中分解出\(p\),那麼\(\varphi(p*m) = \varphi(m) * p\),不然\(\varphi(p * m) =\varphi(m) * (p-1)\)。
再例如默比烏斯函數\(\mu\),只有當\(k=1\)時\(\mu(p^k)=-1\),不然\(\mu(p^k)=0\),和歐拉函數同樣根據\(m\)是否被\(p\)整除判斷。
void Linear_Shaker(int N) { phi[1] = mu[1] = 1; for (int i = 2; i <= N; i++) { if (!phi[i]) { phi[i] = i - 1; mu[i] = -1; prime[cnt++] = i; } for (int j = 0; j < cnt; j++) { if (prime[j] * i > N) break; if (i % prime[j] == 0) { phi[i * prime[j]] = phi[i] * prime[j]; mu[i * prime[j]] = 0; break; } else { phi[i * prime[j]] = phi[i] * (prime[j] - 1); mu[i * prime[j]] = -mu[i]; } } } for (int i = 2; i <= N; i++) { phi[i] += phi[i - 1]; mu[i] += mu[i - 1]; } }
令\(f(i)\)爲\(i\)在\(mod\ p\)意義下的逆元。顯然這個函數是積性函數,咱們可使用線性篩求。可是其實沒有那麼麻煩。
咱們設\(p = ki+r\),那麼\(ki+r \equiv 0 (mod\ p)\),兩邊同時乘\(i^{-1}r^{-1}\),有\(kr^{-1}+i^{-1}\equiv 0\),那麼\(i^{-1} \equiv -kr^{-1}=-\lfloor \frac {p}{i} \rfloor * (p \ mod\ i)^{-1}\),這樣就能夠遞推了。
void getinv(int n) { inv[1] = 1; for(int i = 2; i <= x; i++) inv[i] = (long long)(p - p/i)*inv[p % i] % p; }
有了逆元,咱們就能夠預處理階乘的逆元
\[n!^{-1} \equiv \prod_{k=1}^n k^{-1}\ mod \ p\]
\(\mu\)就是容斥係數。
\[ \mu(n) = \begin{cases} 0, & \text{if $\exists x^2|n$} \\ (-1)^k&n=\prod_\limits{i=1}^{k}p_i \\ \end{cases} \]
\(\mu\)函數也是一個積性函數。
下面的公式能夠從容斥的角度理解。
\[ \sum_{d|n}\mu(d)=[n=1] \]
首先給出Mobius反演的公式:
\[ F(n)=\sum_{d|n}f(d) \Rightarrow f(n)=\sum_{d|n}\mu(\frac{n}{d})F(d) \]
有兩種常見的證實,一種是運用Dirichlet卷積,一種是使用初等方法。
證實:
\[ \sum_{d|n}\mu(d)F(\frac{n}{d}) = \sum_{d|n}\mu(\frac{n}{d})F(d)=\sum_{d|n}\mu(\frac{n}{d})\sum_{k|d}f(k)\\=\sum_{d|n}\sum_{k|d}\mu(\frac{n}{d})f(k)=\sum_{k|n}\sum_{d|\frac{n}{k}}\mu(\frac{n}{kd})f(k)\\ =\sum_{k|n}\sum_{d|\frac{n}{k}}\mu(d)f(k)=\sum_{k|n}[\frac{n}{k} = 1]f(k)=f(n) \]
默比烏斯反演的另外一種形式:
\[ F(n)=\sum_{n|d}f(d)\Rightarrow f(n)=\sum_{n|d}\mu(\frac{d}{n})F(d) \]
這個式子的證實與上式大同小異,我在這裏寫一下關鍵步驟
\[ \sum_{n|d}\sum_{d|k}\mu(\frac{d}{n})f(k)=\sum_{n|k}\sum_{d|\frac{k}{n}}\mu(d)f(k)=f(n) \]
對於一些函數\(f(n)\),若是咱們很難直接求出他的值,而容易求出倍數和或者因數和\(F(n)\),那麼咱們能夠經過默比烏斯反演來求得\(f(n)\)的值
定義兩個數論函數\(f(x)\),\(g(x)\)的\(Dirichlet\)卷積
\[ (f*g)(n)=\sum_{d|n}f(d)g(\frac nd) \]
Dirichlet卷積知足交換律,結合律,分配律,單位元。
運用狄利克雷卷積不難證實默比烏斯反演。
若是能經過狄利克雷卷積構造一個更好計算前綴和的函數,且用於卷積的另外一個函數也易計算,則能夠簡化計算過程。
設\(f(n)\)爲一個數論函數,須要計算\(S(n)=\sum_{i=1}^n f(i)\)。
根據函數\(f(n)\)的性質,構造一個\(S(n)\)關於\(S(\lfloor \frac ni \rfloor)\)的遞推式,以下例:
找到一個合適的數論函數\(g(x)\)
\[ \sum_{i=1}^n\sum_{d|i}f(d)g(\frac id)=\sum_{i=1}^ng(i)S(\lfloor\frac ni\rfloor) \]
能夠獲得遞推式
\[ g(1)S(n)=\sum_{i=1}^n(f*g)(i)-\sum_{i=2}^ng(i)S(\lfloor \frac{n}{i}\rfloor) \]
在遞推計算\(S(n)\)的過程當中,須要被計算的\(S(\lfloor \frac ni \rfloor)\)只有\(O(\sqrt n)\)種。
利用\(\varphi * I=id\)的性質,能夠有:
\[ S(n)=\sum_{i=1}^ni-\sum_{i=2}^nS(\lfloor \frac ni\rfloor) \]
利用\(\mu * I = e\)的性質,能夠有:
\[ S(n)=1-\sum_{i=2}^nS(\lfloor\frac ni\rfloor) \]
ll get_p(int x) { return (x <= m) ? phi[x] : p[n / x]; }; ll get_q(int x) { return (x <= m) ? mu[x] : q[n / x]; }; void solve(ll x) { if (x <= m) return; int i, last = 1, t = n / x; if (vis[t]) return; vis[t] = 1; p[t] = ((x + 1) * x) >> 1; q[t] = 1; while (last < x) { i = last + 1; last = x / (x / i); solve(x / i); p[t] -= get_p(x / i) * (last - i + 1); q[t] -= get_q(x / i) * (last - i + 1); } } //注:本代碼爲了不數組過大,p[]和q[]記錄的是分母的值。
中國剩餘定理給出瞭如下的一元線性同餘方程組有解的斷定條件:
\[ \left\{ \begin{array}{c} x \equiv a_1 \pmod {m_1}\\ x \equiv a_2 \pmod {m_2}\\ \vdots \\ x \equiv a_n \pmod {m_n} \end{array} \right. \]
中國剩餘定理指出,若是模數兩兩互質,那麼方程組有解,而且通解能夠構造獲得:
Gauss消元法就是使用初等行列式變換把原矩陣轉化爲上三角矩陣而後回套求解。給定一個矩陣之後,咱們考察每個變量,找到它的係數最大的一行,而後根據這一行去消除其餘的行。
double a[N][N] void Gauss(){ for(int i=1;i<=n;i++){ int r=i; for(int j=i+1;j<=n;j++) if(abs(a[j][i])>abs(a[r][i])) r=j; if(r!=i) for(int j=1;j<=n+1;j++) swap(a[i][j],a[r][j]); for(int j=i+1;j<=n;j++){ double t=a[j][i]/a[i][i]; for(int k=i;k<=n+1;k++) a[j][k]-=a[i][k]*t; } } for(int i=n;i>=1;i--){ for(int j=n;j>i;j--) a[i][n+1]-=a[j][n+1]*a[i][j]; a[i][n+1]/=a[i][i]; } }
對於xor運算,咱們可使用一樣的方法消元。
另外,xor的話可使用bitset壓位以加速求解。
大步小步算法用於解決:
已知\(A,B,C\),求\(x\)使得, 其中\(C\ is\ a\ prime\).
\[ A^x\equiv B\pmod C \]
咱們令\(x=i\times m-\frac{j}{m}=\lceil \sqrt C \rceil, i \in [1,m], j \in [0,m]\)
那麼原式就變成了\[A^{im}=A^j\times B\] .咱們先枚舉\(j\),把\(A^j \times B\)加入哈希表,而後枚舉\(i\),在表中查找\(A^{im}\),若是找到了,就找到了一個解。時間複雜度爲\(\Theta (\sqrt n)\)。
ll BSGS(ll A, ll B, ll C) { mp.clear(); if(A % C == 0) return -2; ll m = ceil(sqrt(C)); ll ans; for(int i = 0; i <= m; i++) { if(i == 0) { ans = B % C; mp[ans] = i; continue; } ans = (ans * A) % C; mp[ans] = i; } ll t = pow(A, m, C); ans = t; for(int i = 1; i <= m; i++) { if(i != 1)ans = ans * t % C; if(mp.count(ans)) { int ret = i * m % C - mp[ans] % C; return (ret % C + C)%C; } } return -2; }
\[ (a+b)^n=\sum_{i=0}^n C_n^i a^{i}b^{n-i} \]
其中\(C_n^m\)爲二項式係數,知足幾個結論:
\[ C_n^i=C_n^{n-i} \]
\[ C_{n+1}^m=C_n^m+C_n^{m-1} \]
\[ \sum_{i=0}^nC_n^i=2^n \]
\[ C_n^k=\frac{k}{n}C_{n-1}^{k-1} \]
隔板法與插空法
n元素集合的循環r排列的數目是\(\frac{P_n^r}r\)
多重集合全排列\[\frac{n!}{\prod_i n_i!}\]
多重集合的組合,無限重複數,設S是有k種類型對象的多重集合,r組合的個數爲\[C_{r+k-1}^r=C_{r+k-1}^{k-1}\]。
\(Lucas\)定理(p爲素數) :
\[ C_n^m \equiv C_{n / p}^{m/p} \times C_{n \ mod\ p}^{m\ mod\ p} \pmod p \]
int C(int n, int m, int P) { if (n < m) return 0; return (ll)fac[n] * inv(fac[n-m], P) % P * inv(fac[m], P)%P; } int lucas(int n, int m, int P) { if(!n && !m) return 1; return (ll)lucas(n/P, m/P, P) * C(n%P, m%P, P) % P; }
\[ |\cup_{i=1}^nA_i|=\sum_{i=1}^nA_i-\sum_{i,j:i\not=j}|A_i \cap A_j|+\sum_{i,j,k:i\not=j\not=k}|A_i \cap A_j \cap A_k|-\cdots \pm |A_1 \cap \cdots \cap A_n| \]
void calc(){ for(int i = 1; i < (1 << ct); i++) { //do sth for(int j = 0; j < ct; j++) { if(i & (1 << j)) { cnt++; //do sth } } if(cnt & 1) ans += tmp; else ans -= tmp; } }
若a,b是整數,且(a,b)=d,那麼對於任意的整數x,y,ax+by都必定是d的倍數,特別地,必定存在整數x,y,使ax+by=d成立。
它的一個重要推論是:a,b互質的充要條件是存在整數x,y使ax+by=1.
鬆弛的交換律:若對於每個整數\(n\),都剛好存在一個整數\(k\)使得\(p(k)=n\),那麼交換律一樣成立。
擾動法,用於計算一個和式,其思想是從一個未知的和式開始,並記他爲\(S_n\):\[S_n=\sum_{0 \leqslant k \leqslant n} a_k\],而後,經過將他的最後一項和第一項分離出來,用兩種方法重寫\(S_{n+1}\),這樣咱們就獲得了一個關於\(S_n\)的方程,就能夠獲得其封閉形式了。
一個常見的交換
\[\sum_{d|n}f(d)=\sum_{d|n}f(\frac{n}{d})\]
交換求和次序:
\[ \sum_j\sum_ka_{j,k}[P(j,k)]=\sum_{P(j,k)}a_{j,k}=\sum_k\sum_ja_{j,k}[P(j,k)] \]
通常分配律:\[\sum_{j \in J, k \in K}a_jb_k=(\sum_{j \in J}a_j)(\sum_{k \in K}b_k)\]
\(Rocky\ Road\)
\[ \sum_{j \in J}\sum_{k \in K(j)}a_{j,k}=\sum_{k \in K^{'}}\sum_{j \in J^{'}}a_{j,k} \]
\[ [j \in J][k \in K(j)]=[k \in K^{'}][j \in J^{'}(k)] \]
事實上,這樣的因子分解老是可能的:咱們能夠設\(J=K^{'}\)是全部整數的集合,而\(K(j)\)和\(J^{'}(K)\)是與操控二重和式的性質\(P(j,k)\)相對應的集合。下面是一個特別有用的分解。
\[[1\leqslant j \leqslant n][j \leqslant k \leqslant n] = [1 \leqslant j \leqslant k \leqslant n] = [1 \leqslant k \leqslant n][1 \leqslant j \leqslant k]\]
一個常見的分解
\[ \sum_{d|n}\sum_{k|d}=\sum_{k|m}\sum_{d|\frac{m}{k}} \]
一個技巧
若是咱們有一個包含\(k+f(j)\)的二重和式,用\(k-f(j)\)替換\(k\)並對\(j\)求和比較好。
例如,在bzoj2301中,咱們最終解出了\[f(n, m)=\sum_{1 \leqslant d \leqslant min(n, m)}\mu(d)\lfloor \frac {n}{d} \rfloor \lfloor \frac {m}{d} \rfloor\]咱們就可使用杜教篩計算出默比烏斯函數的前綴和,計算出商與除以i相同的最多延伸到哪裏,下一次直接跳過這一段就行了。下面是這個題的一段程序。
int calc(int n, int m) { int ret = 0, last; if(n > m) std::swap(n, m); for(int i = 1; i <= n; i = last + 1) { //i就至關於原式中的d last = min(n / (n/i), m / (m/i)); //last計算了商與除以i相同的最多延伸到哪裏,不難證實這樣計算的正確性 ret += (n / i) * (m / i) * (sum[last] - sum[i-1]); } return ret; }
\[ a^{p-1}\equiv1\pmod p \]
條件:\(p\ is\ prime\ and\ (a,p)=1\)
\[ a^{\varphi(p)}\equiv 1\pmod p \]
條件:\(a,p \in \mathbb{Z^+}, (a, p)=1\)
\[ (p-1)!\equiv-1\pmod p \Leftrightarrow p\ is\ prime \]
\[ S=n+\frac s2-1 \]
(其中\(n\)表示多邊形內部的點數,\(s\)表示多邊形邊界上的點數,\(S\)表示多邊形的面積)
inline ll mul(ll a, ll b) { ll x = 0; while (b) { if (b & 1) x = (x + a) % p; a = (a << 1) % p; b >>= 1; } return x; }
inline ll pow(ll a, ll b, ll p) { ll x = 1; while (b) { if (b & 1) x = mul(x, a); a = mul(a, a); b >>= 1; } return x; }
第一步:任意給定兩個正整數;判斷它們是否都是偶數。如果,則用2約簡;若不是則執行第二步。
第二步:以較大的數減較小的數,接着把所得的差與較小的數比較,並以大數減少數。繼續這個操做,直到所得的減數和差相等爲止。
則第一步中約掉的若干個2與第二步中等數的乘積就是所求的最大公約數。
根據費馬小定理(p是質數),
int inv(int x, int P){return pow(x, P-2, P);}
或使用擴展歐幾里德:
int inv(int x, int P) { int d, a, b; gcd(x, P, d, a, b); return d == 1 ? (a+P)%P : -1; }
定義\[SG(x)=mex(S)\],其中\(S\)是\(x\)的後繼狀態的\(SG\)函數集合,\(mex(S)\)表示不在\(S\)內的最小非負整數。
組合遊戲和的\(SG\)函數等於各子游戲\(SG\)函數的\(Nim\)和。
快速傅立葉變換(FFT)用於求解多項式的卷積.
考慮將多項式\(A_1(x)=a_0+a_2x^2+a_4x^4+\cdots+a_{n-2}x^{\frac n2 -1}\)
\[A_2(x)=a_1+a_3x+a_5x^2+\cdots+a_{n-1}x^{\frac n2 -1}\]
則有\[A(x)=A_1(x)+xA_2(x)\]
有\[A(\omega_n^k)=A_1(\omega_n^{2k})+\omega_n^kA_2(\omega_n^{2k})\]
有\[A(\omega_n^{k+\frac n2})=A_1{\omega_\frac n2^k-\omega_n^kA_2(\omega_\frac n2 ^ k)}\].
注意到,當\(k\)取遍\([0,\frac n2 -1]\)時,\(k\)和\(k+\frac n2\)取遍了\([0,n-1]\),也就是說,若是已知\(A_1(x)\)和\(A_2(x)\)在\(\omega_{n/2}^0,\omega_{n/2}^1,\cdots,\omega_{n/2}^{n/2-1}\)處的點值,就能夠在\(O(n)\)的時間內求得\(A(x)\)在\(\omega_n^0,\omega_n^1,\cdots,\omega_n^{n-1}\)處的取值。而關於 \(A_1(x)\) 和 \(A_2(x)\) 的問題都是相對於原問題規模縮小了一半的子問題,分治的邊界爲一個常數項\(a_0\).
該算法的複雜度爲\(O(nlogn)\).
設\((y_0,y_1,\cdots,y_{n-1})\)爲\((a_0,\cdots,a_{n-1})\)的快速傅立葉變換. 考慮另外一個向量\((c_0,\cdots,c_{n-1})\)知足
\[c_k=\sum_{i=0}^{n-1}y_i(\omega_n^{-k})^i\].
即多項式\(B(x)=y_0+y_1x+\cdots+y_{n-1}x^{n-1}\)在\(\omega_n^0,\cdots,\omega_n^{-(n-1)}\)處的點值表示.
能夠獲得(證實略)
\[a_i=\frac 1n c_i\].
因此,使用單位根的倒數代替單位根,作一次相似快速傅里葉變換的過程,再將結果每一個數除以\(n\),即爲傅里葉逆變換的結果。
FFT有兩種常見的代碼實現: 遞歸版本和迭代版本,通常來說遞歸效率不好,但因爲我很是菜,一輪省選就先使用遞歸版本騙分.迭代版本之後會更新.
const double PI = acos(-1); bool inversed = false; inline std::complex<double> omega(const int n, const int k) { if (!inversed) return std::complex<double>(cos(2 * PI / n * k), sin(2 * PI / n * k)); else return std::conj( std::complex<double>(cos(2 * PI / n * k), sin(2 * PI / n * k))); } void fft(std::complex<double> *a, std::complex<double> *ans, int n) { if (n == 1) { ans[0] = a[0]; return; } std::complex<double> buf[maxn]; int m = n >> 1; for (int i = 0; i < m; i++) { buf[i] = a[i * 2]; buf[i + m] = a[i * 2 + 1]; } std::complex<double> tmp[maxn]; fft(buf, tmp, m); fft(buf + m, tmp + m, m); for (int i = 0; i < m; i++) { std::complex<double> x = omega(n, i); ans[i] = tmp[i] + x * tmp[i + m]; ans[i + m] = tmp[i] - x * tmp[i + m]; } } void solve() { //sth while (N < n + 1) N <<= 1; N <<= 1; fft(a, ans1, N); fft(b, ans2, N); std::complex<double> ans3[maxn]; for (int i = 0; i < N; i++) ans3[i] = ans1[i] * ans2[i]; std::complex<double> tmp[maxn]; inversed = true; fft(ans3, tmp, N); for (int i = 0; i < N; i++) c[i] = tmp[i].real() / N; //sth }
$\mathtt{COPYRIGHT}© \mathtt{2017,KONJAC,MIT LICENSE} $