#define __int128 i128 inline i128 read(){ i128 x = 0; bool f = false; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = true; ch = getchar(); } while (ch >= '0' && ch <= '9') { x = x * 10 + (ch - '0'); ch = getchar(); } return f ? -x : x; }
voidprint(i128 x){ if (x == 0) { putchar('0'); return; } if (x < 0) { putchar('-'); x = -x; } char buf[40]; //注意位数 int len = 0; while (x > 0) { buf[len++] = x % 10 + '0'; x /= 10; } for (int i = len - 1; i >= 0; i--) { putchar(buf[i]); } }
constint N = 100010; // 操作的总次数上限 int c[N]; int n; // 离散化后不同元素的总个数(也是树状数组的边界)
intlowbit(int x){ return x & -x; }
// 树状数组:单点修改 voidadd(int x, int k){ while (x <= n) { c[x] += k; x += lowbit(x); } }
// 树状数组:求前缀和 intgetsum(int x){ int ans = 0; while (x > 0) { ans += c[x]; x -= lowbit(x); } return ans; }
// 树状数组:求第 k 小 (倍增法 O(log N)) intkth(int k){ int pos = 0; int sum = 0; for (int i = 20; i >= 0; i--) { // 2^20 > 10^6,根据 N 调整 int next_pos = pos + (1 << i); if (next_pos <= n && sum + c[next_pos] < k) { pos = next_pos; sum += c[next_pos]; } } return pos + 1; }
usingnamespace std; constint N = 1e5+10; #define int int int n,m,seed,vmax; int a[N]; structnode{ int l,r; mutableint v; node(constint &il,constint &ir,constint &iv):l(il),r(ir),v(iv){}; booloperator <(const node &o) const {return l<o.l;} };
set<node> odt;
autosplit(int x){ //找到区间左端点大于等于x的第一个区间 auto it = odt.lower_bound(node(x,0,0)); //判断是否刚好不用分割 if(it!=odt.end()&&it->l == x) return it; --it; //得到左端点小于x的区间 int l=it->l,r=it->r,v=it->v; odt.erase(it); //删去本区间 //得到两区间 odt.insert(node(l,x-1,v)); //返回分割后的左端点为x的区间 return odt.insert(node(x,r,v)).first; }
//区间修改 voidassign(int l,int r,int v){ auto itr=split(r+1); auto itl=split(l); odt.erase(itl,itr); //删去区间内的所有小区间 odt.insert(node(l,r,v)); //插入大区间 }
//区间加法 voidadd(int l,int r,int v){ auto itr=split(r+1); auto itl=split(l); for(;itl!=itr;itl++){ itl->v += v; } }
intqpow(int v,int x,int y){ int res = 1; while(x>0){ if(x&1){ res = res * v % y; } v = (v%y)*(v%y) % y; x >>= 1; } return res; }
//区间求a^x和,单纯求和换掉qpow voidperform2(int l,int r,int x,int y){ auto itr = split(r+1); auto itl = split(l); int res = 0; for(;itl!=itr;itl++){ int l=itl->l,r=itl->r,v=itl->v; res += (qpow(v,x,y)*(r-l+1))%y; res %= y; } cout << res << endl; }
/* 值的种类少的情况下,接在assign 操作后 void merge(set<node>::iterator a){ //查看是否能合并周围结点,如果能就合并 auto l = a,r = next(a); if(prev(a)->v == a->v){ l = prev(a); } if(next(a)->v == a->v){ r = next(r); } if(next(l) != r){ int il = l->l,ir = prev(r)->r,iv = l->v; odt.erase(l,r); odt.insert(node(il,ir,iv)); } } */
intrnd(){ //这个是求原题的随机数组,不用管 int ret = seed; seed = (seed * 7 + 13) % 1000000007; return ret; }
for(int i=1;i<=m;i++){ int op = (rnd() % 4) + 1; int l = (rnd() % n) + 1; int r = (rnd() % n) + 1; int x,y; if (l > r) swap(l, r);
if (op == 3) x = (rnd() % (r - l + 1)) + 1; else x = (rnd() % vmax) + 1;
if (op == 4) y = (rnd() % vmax) + 1;
if(op==1) add(l,r,x); //区间加法 elseif(op==2) assign(l,r,x); //区间赋值 elseif(op==3) perform1(l,r,x); //输出[l,r]中第x个最小值 elseif(op==4) perform2(l,r,x,y); //\sum a_i^x mod y } }
动态规划
LIS
1 2 3 4 5 6
//d为辅助数组,需要存储要单独记录 for(int i=0;i<n;i++){ int pos = lower_bound(d+1,d+len+1,a[i])-d; d[pos] = a[i]; len = max(p,len); }
背包DP
多重背包二进制处理
1 2 3 4 5 6 7 8 9 10 11 12 13
index = 0; for (int i = 1; i <= m; i++) { int c = 1, p, h, k; cin >> p >> h >> k; while (k > c) { k -= c; list[++index].w = c * p; list[index].v = c * h; c *= 2; } list[++index].w = p * k; list[index].v = h * k; }
二维费用背包
1 2 3 4
for (int k = 1; k <= n; k++) for (int i = m; i >= mi; i--) // 对经费进行一层枚举 for (int j = t; j >= ti; j--) // 对时间进行一层枚举 dp[i][j] = max(dp[i][j], dp[i - mi][j - ti] + 1);
分组背包
1 2 3 4 5 6
for (int k = 1; k <= ts; k++) // 循环每一组 for (int i = m; i >= 0; i--) // 循环背包容量 for (int j = 1; j <= cnt[k]; j++) // 循环该组的每一个物品 if (i >= w[t[k][j]]) // 背包容量充足 dp[i] = max(dp[i], dp[i - w[t[k][j]]] + c[t[k][j]]); // 像0-1背包一样状态转移
int fa[N]; // 并查集 int n,m; // 顶点数与边数 int cnt; //记录当前边数
structedge{ int u,v,w; booloperator<(edge b){ return w<b.w; //方便对边权比较 } };
edge g[M];
voidkruskal(){ int tot = 0; //存已选了的边数 int ans = 0; //总代价 for(int i=1;i<=m;i++){ int x = find(g[i].u),y = find(g[i].v); if(x!=y){ //二者不在一棵树上 tot++; ans += g[i].w; unite(x,y); } if(tot==n-1){ cout << ans << '\n'; //输出总代价 return; } } cout << "orz" << '\n'; //不存在 }
intmain(){ cin >> n >> m; for(int i=1;i<=m;i++){ int u,v,w; cin >> u >> v >> w; add(u,v,w); } iota(fa+1,fa+n+1,1); //让[1...n]=1...n sort(g+1,g+m+1); kruskal(); }
最短路
Floyd
1 2 3 4 5 6 7
for (k = 1; k <= n; k++) { for (x = 1; x <= n; x++) { for (y = 1; y <= n; y++) { f[x][y] = min(f[x][y], f[x][k] + f[k][y]); } } }
constint N = 500005; vector<int> adj[N]; int fa[N][21]; // fa[i][j] 表示节点 i 向上跳 2^j 步到达的祖先 int dep[N]; // 深度
// 1. 预处理:通过一次 DFS 计算深度和 2^0 的祖先(即父亲) voiddfs(int u, int f){ dep[u] = dep[f] + 1; fa[u][0] = f; // 关键:在这里直接预处理出当前节点的所有 2^k 倍祖先 for (int i = 1; i <= 20; i++) { fa[u][i] = fa[fa[u][i - 1]][i - 1]; } for (int v : adj[u]) { if (v != f) dfs(v, u); } }
intget_lca(int u, int v){ // 保证 u 是更深的那一个 if (dep[u] < dep[v]) swap(u, v); // 先跳到同一高度 for (int i = 20; i >= 0; i--) { if (dep[fa[u][i]] >= dep[v]) { u = fa[u][i]; } } if (u == v) return u;
// 一起向上跳,直到跳到 LCA 的下一层 for (int i = 20; i >= 0; i--) { if (fa[u][i] != fa[v][i]) { u = fa[u][i]; v = fa[v][i]; } } return fa[u][0]; // 返回它们的父亲即为 LCA }
// sz: 子树大小 // big: 重儿子 // col: 结点颜色 // L[u]: 结点 u 的 DFS 序 // R[u]: 结点 u 子树中结点的 DFS 序的最大值 // Node[i]: DFS 序为 i 的结点 // totdfn: 节点计数器,也是当前遍历过节点的 DFS 序最大值 // ans: 存答案 // cnt[i]: 颜色为 i 的结点个数 // totColor: 目前出现过的颜色个数 int sz[N], big[N], col[N], L[N], R[N], Node[N], totdfn; int ans[N], cnt[N], totColor;
voidadd(int u){ if (cnt[col[u]] == 0) ++totColor; cnt[col[u]]++; }
voiddel(int u){ cnt[col[u]]--; if (cnt[col[u]] == 0) --totColor; }
intgetAns(){ return totColor; }
voiddfs0(int u, int p){ L[u] = ++totdfn; Node[totdfn] = u; sz[u] = 1; for (int v : g[u]) if (v != p) { dfs0(v, u); sz[u] += sz[v]; if (!big[u] || sz[big[u]] < sz[v]) big[u] = v; } R[u] = totdfn; }
voiddfs1(int u, int p, bool keep){ // 计算轻儿子的答案 for (int v : g[u]) if (v != p && v != big[u]) { dfs1(v, u, false); } // 计算重儿子答案并保留计算过程中的数据(用于继承) if (big[u]) { dfs1(big[u], u, true); } for (int v : g[u]) if (v != p && v != big[u]) { // 子树结点的 DFS 序构成一段连续区间,可以直接遍历 for (int i = L[v]; i <= R[v]; i++) { add(Node[i]); } } add(u); ans[u] = getAns(); if (!keep) { for (int i = L[u]; i <= R[u]; i++) { del(Node[i]); } } }
intmain(){ scanf("%d", &n); for (int i = 1; i < n; i++) { int u, v; scanf("%d%d", &u, &v); g[u].push_back(v); g[v].push_back(u); } for (int i = 1; i <= n; i++) scanf("%d", &col[i]); dfs0(1, 0); dfs1(1, 0, false); scanf("%d", &m); for (int i = 1; i <= m; i++) { int q; scanf("%d", &q); printf("%d\n", ans[q]); } return0; }
structAC_Automaton { // 【上半区:Trie 树部分】 int nex[N][26]; int cnt[N]; // 记录该节点结尾的单词数量 int idx; // 节点分配器
voidclear(){ for (int i = 0; i <= idx; i++) { memset(nex[i], 0, sizeof(nex[i])); cnt[i] = 0; fail[i] = 0; } idx = 0; }
// 插入模式串 voidinsert(const string& s){ int p = 0; for (char ch : s) { int c = ch - 'a'; if (!nex[p][c]) nex[p][c] = ++idx; p = nex[p][c]; } cnt[p]++; } // 精准查找某一个模式串是否完整存在 boolfind(const string& s){ int p = 0; for (char ch : s) { int c = ch - 'a'; if (!nex[p][c]) { returnfalse; } p = nex[p][c]; } // AC自动机中用 cnt 数组代替了 exist // 如果查找前缀包含s的串是否存在,只要循环顺利结束就行(return true) return cnt[p] > 0; }
// 【下半区:AC 自动机专属部分】 int fail[N]; // 失配指针
// 构建 fail 指针 (BFS 遍历字典树) voidbuild(){ queue<int> q; for (int i = 0; i < 26; i++) { if (nex[0][i]) { q.push(nex[0][i]); } } while (!q.empty()) { int u = q.front(); q.pop(); for (int i = 0; i < 26; i++) { int v = nex[u][i]; if (v) { fail[v] = nex[fail[u]][i]; q.push(v); } else { nex[u][i] = nex[fail[u]][i]; } } } }
// 多模式串匹配主逻辑 intquery(const string& s){ int ans = 0; int p = 0; for (char ch : s) { p = nex[p][ch - 'a']; int temp = p; while (temp && cnt[temp] != -1) { ans += cnt[temp]; // 注意:这里会破坏原有的 cnt! // 如果题目要求多次调用 query,请去掉这行,或使用额外的 vis 数组打标记 cnt[temp] = -1; temp = fail[temp]; } } return ans; } };
回文串预处理:在一些复杂的字符串题目中,结合正反跑两遍
Z 函数,可以快速判断各种前缀回文拼接问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
vector<int> z_function(string s){ int n = (int)s.length(); vector<int> z(n); for (int i = 1, l = 0, r = 0; i < n; ++i) { if (i <= r && z[i - l] < r - i + 1) { z[i] = z[i - l]; } else { z[i] = max(0, r - i + 1); while (i + z[i] < n && s[z[i]] == s[i + z[i]]) ++z[i]; } if (i + z[i] - 1 > r) l = i, r = i + z[i] - 1; } return z; }
vector<int> add(vector<int>& A, vector<int>& B){ vector<int> C; int t = 0; // 进位 for (int i = 0; i < A.size() || i < B.size() || t != 0; i++) { if (i < A.size()) t += A[i]; if (i < B.size()) t += B[i]; C.push_back(t % 10); t /= 10; } return C; }
// 比较 A 是否大于等于 B boolcmp(vector<int>& A, vector<int>& B){ if (A.size() != B.size()) return A.size() > B.size(); for (int i = A.size() - 1; i >= 0; i--) { if (A[i] != B[i]) return A[i] > B[i]; } returntrue; }
// 高精度减法 (前提:保证 A >= B) vector<int> sub(vector<int>& A, vector<int>& B){ vector<int> C; int t = 0; // 借位 for (int i = 0; i < A.size(); i++) { t = A[i] - t; if (i < B.size()) t -= B[i]; C.push_back((t + 10) % 10); // 巧妙处理:如果 t < 0,加上 10 就是借位后的值;如果 t >= 0,加 10 模 10 依然不变 if (t < 0) t = 1; // 产生了借位 else t = 0; } // 去除前导零 while (C.size() > 1 && C.back() == 0) C.pop_back(); return C; }
vector<int> mul(vector<int>& A, vector<int>& B){ // 结果的最大长度为 A.size() + B.size() vector<int> C(A.size() + B.size(), 0); for (int i = 0; i < A.size(); i++) { for (int j = 0; j < B.size(); j++) { C[i + j] += A[i] * B[j]; } } // 统一处理进位 int t = 0; for (int i = 0; i < C.size(); i++) { t += C[i]; C[i] = t % 10; t /= 10; } // 去除前导零 while (C.size() > 1 && C.back() == 0) C.pop_back(); return C; }
// A 是高精度,b 是普通整数。r 用来返回余数 vector<int> div(vector<int>& A, int b, int& r){ vector<int> C; r = 0; for (int i = A.size() - 1; i >= 0; i--) { // 从最高位开始算 r = r * 10 + A[i]; C.push_back(r / b); // 存入商 r %= b; } // 因为除法是从高位开始除的,C 里面的顺序是正序(高位在低索引),为了统一格式需要 reverse reverse(C.begin(), C.end()); // 去除前导零 while (C.size() > 1 && C.back() == 0) C.pop_back(); return C; }
快速幂
若无需模,请无视 \(p\)
1 2 3 4 5 6 7 8 9
intqpow(int a,int b,int p){ int res = 1; while(b){ if(b&1) res = res*a %p; a = a*a %p; b >>= 1; } return res; }
数论
模数放大技巧
\[\frac{A}{B} \pmod m = \frac{A \pmod{B
\times m}}{B}\]
intcrt(){ int x = 0; int pp = 1; // 所有模数的乘积 for (int i = 0; i < n; i++) pp *= p[i]; for (int i = 0; i < n; i++) { int m = pp / p[i]; int inv = inverse(m, p[i]); // 用ex_gcd求模逆元 int c = (m * inv) % pp; x = (x + (a[i] % pp) * c) % pp; } return (x + pp) % pp; }
structMatrix { longlong mat[MAT_SIZE][MAT_SIZE]; Matrix() { memset(mat, 0, sizeof(mat)); } // 重载乘法 Matrix operator*(const Matrix& b) const { Matrix res; for (int i = 0; i < MAT_SIZE; i++) { for (int k = 0; k < MAT_SIZE; k++) { if (!mat[i][k]) continue; // 常数优化 for (int j = 0; j < MAT_SIZE; j++) { res.mat[i][j] = (res.mat[i][j] + mat[i][k] * b.mat[k][j]) % MOD; } } } return res; } };
// 矩阵快速幂 Matrix mat_qpow(Matrix a, longlong b){ Matrix res; for (int i = 0; i < MAT_SIZE; i++) res.mat[i][i] = 1; // 初始化为单位阵 while (b) { if (b & 1) res = res * a; a = a * a; b >>= 1; } return res; }