2024.02 别急记录
1. WC/CTSC2024 - 水镜 [省选/NOI-]
设
; 即 ; 即 ; 即 。
那么由 1,2 可得若
类似地总共四条:
- 若
则一定会取 ; - 若
则一定会取 ; - 若
则一定会取 ; - 若
则一定会取 。
那么我们接着考虑
那么我们就可以对于每个
- 若
; - 若
;
那么一个区间合法当且仅当
点击查看代码
const int N = 5e5 + 10; ll lw[20][N], up[20][N], h[N]; int n; bool chk(int L, int R){ if(L > R){ return 1; } int k = 31 ^ __builtin_clz(R-L+1); ll Lm = max(lw[k][L], lw[k][R-(1<<k)+1]); ll Rm = min(up[k][L], up[k][R-(1<<k)+1]); return Lm < Rm; } void solve(){ read(n); for(int i = 1; i <= n; ++ i){ read(h[i]); } lw[0][1] = lw[0][n] = -1e18; up[0][1] = up[0][n] = 1e18; for(int i = 2; i < n; ++ i){ lw[0][i] = -1e18; up[0][i] = 1e18; if(h[i-1] <= h[i] && h[i] >= h[i+1]){ lw[0][i] = min(h[i-1], h[i+1]) + h[i]; } if(h[i-1] >= h[i] && h[i] <= h[i+1]){ up[0][i] = max(h[i-1], h[i+1]) + h[i]; } } for(int i = 1; i < 20; ++ i){ for(int j = 1; j + (1 << i) - 1 <= n; ++ j){ lw[i][j] = max(lw[i-1][j], lw[i-1][j+(1<<i-1)]); up[i][j] = min(up[i-1][j], up[i-1][j+(1<<i-1)]); } } ll ans = 0; for(int i = 1; i <= n; ++ i){ int l = i, r = n; while(l < r){ int mid = l + r + 1 >> 1; if(chk(i + 1, mid - 1)){ l = mid; } else { r = mid - 1; } } ans += l - i; } println(ans); }
2. CTSC2017 - 吉夫特 [省选/NOI-]
简单题啊。
一个经典结论是
proof
根据卢卡斯定理有
然后我们观察到如果
否则这个不会变为
然后就能写出转移方程:
点击查看代码
const int N = 262144 + 10; const ll P = 1e9 + 7; int n, a[N], p[N]; ll f[N], ans; void solve(){ read(n); memset(p, 0x3f, sizeof(p)); for(int i = 1; i <= n; ++ i){ read(a[i]); p[a[i]] = i; } for(int i = 0; i <= n; ++ i){ int s = 262143 - a[i]; for(int j = s; j > 0; j = (j - 1) & s){ f[a[i]] = (f[a[i]] + (p[a[i]+j] < i ? f[a[i]+j]+1 : 0)) % P; } ans = (ans + f[a[i]]) % P; } println(ans); }
3. CF1918G - Permutation of Given [*2700]
无聊构造题。
首先
proof
当当
然后考虑一个合法的长度为
那么若在序列末尾添加
那么我们只用找到
点击查看代码
const int N = 1e6 + 10; int n, a[N]; void solve(){ read(n); if(n == 3 || n == 5){ println_cstr("NO"); } else if(n & 1){ println_cstr("YES"); a[1] = a[2] = -3; a[3] = 2; a[4] = a[6] = 1; a[5] = -1; a[7] = -2; for(int i = 8; i <= n; i += 2){ a[i] = - a[i-1]; a[i+1] = a[i-2] - a[i-1]; } for(int i = 1; i <= n; ++ i){ printk(a[i]); } } else { println_cstr("YES"); a[1] = 1; a[2] = 2; for(int i = 3; i <= n; i += 2){ a[i] = - a[i-1]; a[i+1] = a[i-2] - a[i-1]; } for(int i = 1; i <= n; ++ i){ printk(a[i]); } } }
4. COCI2018-2019#4 - Akvizna [NOI/NOI+/CTSC]
wqs 二分+斜率优化。
首先考虑如果没有
可以斜率优化。具体地,用单调队列维护
然后加上
点击查看代码
const int N = 1e5 + 10; const double eps = 1e-16; int n, k, q[N], g[N]; double f[N]; double slp(int x, int y){ return (f[x] - f[y]) * 1.0 / (x - y); } bool chk(double x){ int l = 1, r = 0; q[1] = 0; memset(g, 0, sizeof(g)); for(int i = 1; i <= n; ++ i){ while(l < r && slp(q[l+1], q[l]) > 1.0 / i + eps){ ++ l; } f[i] = f[q[l]] + (i - q[l]) * 1.0 / i - x; g[i] = g[q[l]] + 1; while(l < r && slp(q[r], q[r-1]) + eps < slp(i, q[r])){ -- r; } q[++r] = i; } return g[n] >= k; } void solve(){ scanf("%d%d", &n, &k); double l = 0, r = 1e6; while(l + eps < r){ double mid = (l + r) / 2; if(chk(mid)){ l = mid; } else { r = mid; } } printf("%.9lf\n", f[n] + l * k); }
5. LuoguP5633 - 最小度限制生成树 [省选/NOI-]
wqs 二分。
考虑给每条连接到
无解情况:
点击查看代码
const int N = 1e5 + 10, M = 5e5 + 10; int n, m, s, k, deg, fa[N], tx, ty; ll res; struct edge{ int u, v, w; } e[M], E[M]; bool cmp(edge x, edge y){ return x.w < y.w; } int gf(int x){ return x == fa[x] ? x : fa[x] = gf(fa[x]); } int chk(int x){ for(int i = 1; i <= n; ++ i){ fa[i] = i; } ll ans = 0, cnt = 0, i = 1, j = 1; while(i <= tx || j <= ty){ if(i <= tx && (e[i].w < E[j].w - x || j > ty)){ if(gf(e[i].u) != gf(e[i].v)){ fa[gf(e[i].u)] = gf(e[i].v); ans += e[i].w; } ++ i; } else { if(gf(E[j].u) != gf(E[j].v)){ fa[gf(E[j].u)] = gf(E[j].v); ans += E[j].w - x; ++ cnt; } ++ j; } } res = ans; return cnt; } void solve(){ read(n, m, s, k); for(int i = 1; i <= n; ++ i){ fa[i] = i; } for(int i = 1; i <= m; ++ i){ int u, v, w; read(u, v, w); fa[gf(u)] = gf(v); if(u == s || v == s){ ++ deg; E[++ty] = { u, v, w }; } else { e[++tx] = { u, v, w }; } } sort(e + 1, e + tx + 1, cmp); sort(E + 1, E + ty + 1, cmp); int cnt = 0; for(int i = 1; i <= n; ++ i){ if(gf(i) == i){ ++ cnt; } } if(deg < k || cnt > 1 || chk(-1e5) > k){ println_cstr("Impossible"); return; } int l = -1e5, r = 1e5; while(l < r){ int mid = l + r >> 1; if(chk(mid) >= k){ r = mid; } else { l = mid + 1; } } chk(l); println(res + k * l); }
6. POI2014 - PAN-Solar Panels [提高+/省选-]
显然可以四维数论分块。
但是发现对于
点击查看代码
int T, a, b, c, d; void solve(){ read(T); while(T--){ read(a, b, c, d); if(b > d){ swap(a, c); swap(b, d); } int ans = 0; for(int l = 1, r; l <= b; l = r + 1){ r = min(b / (b / l), d / (d / l)); if((a-1) / r < b / r && (c-1) / r < d / r){ ans = max(ans, r); } } println(ans); } }
7. Ynoi ER 2021 - TEST_152 [省选/NOI-]
考虑从左往右执行操作的同时使用树状数组记录:第
接着考虑如何快速操作:使用 set
维护序列连续段及其值、最后修改的时间,然后每次修改找到那些与之有交的序列段进行修改。可以发现每次至多增加两段,故总段数是
复杂度
点击查看代码
const int N = 5e5 + 10; int n, m, q, L[N], R[N], v[N]; vector<pair<int, int> > qr[N]; tuple<int, int, int> seq[N]; ll bit[N], ans[N]; void add(int x, ll v){ ++ x; while(x <= n + 1){ bit[x] += v; x += x & -x; } } ll ask(int x){ ++ x; ll rs = 0; while(x){ rs += bit[x]; x -= x & -x; } return rs; } ll ask(int l, int r){ return ask(r) - ask(l-1); } void solve(){ read(n, m, q); for(int i = 1; i <= n; ++ i){ read(L[i], R[i], v[i]); } for(int i = 1; i <= q; ++ i){ int l, r; read(l, r); qr[r].emplace_back(l, i); } set<int> st; st.insert(1); seq[1] = { n, 0, 0 }; for(int i = 1; i <= n; ++ i){ while(true){ auto it = st.upper_bound(R[i]); if(it == st.begin()){ break; } -- it; int l = (*it), r, cl, vl; tie(r, cl, vl) = seq[l]; if(L[i] <= l && r <= R[i]){ add(cl, -(ll)vl * (r-l+1)); st.erase(it); } else if(L[i] <= l && R[i] < r && l <= R[i]){ add(cl, -(ll)vl * (R[i]-l+1)); st.erase(it); st.insert(R[i]+1); seq[R[i]+1] = { r, cl, vl }; } else if(l < L[i] && r <= R[i] && L[i] <= r){ add(cl, -(ll)vl * (r-L[i]+1)); seq[l] = { L[i]-1, cl, vl }; break; } else if(l < L[i] && R[i] < r){ add(cl, -(ll)vl * (R[i]-L[i]+1)); seq[l] = { L[i]-1, cl, vl }; st.insert(R[i]+1); seq[R[i]+1] = {r, cl, vl}; break; } else { break; } } st.insert(L[i]); seq[L[i]] = { R[i], i, v[i] }; add(i, (ll)v[i] * (R[i]-L[i]+1)); for(auto j : qr[i]){ ans[j.second] = ask(j.first, i); } } for(int i = 1; i <= q; ++ i){ println(ans[i]); } }
8. CF627F - Island Puzzle [*3400]
思路不难的逆天角盒题!
首先考虑不加边的情况,容易发现将
那么将
但是,有可能有的路径可以省去!比如
点击查看代码
const int N = 2e5 + 10; int n, a[N], b[N], a0, b0; vector<int> g[N]; int st[N], tp; bool dfs1(int x, int fa){ st[++tp] = x; if(x == b0){ return 1; } for(int i : g[x]){ if(i != fa){ if(dfs1(i, x)){ return 1; } } } -- tp; return 0; } int nd[N], dep[N], fat[N], ind[N]; void dfs2(int x, int fa){ fat[x] = fa; dep[x] = dep[fa] + 1; for(int i : g[x]){ if(i != fa){ dfs2(i, x); } } } int stt[N], tpp, as[N], bs[N], so[N]; bool dfs3(int x, int fa, int gl){ stt[++tpp] = x; if(x == gl){ return 1; } for(int i : g[x]){ if(i != fa && nd[i]){ if(dfs3(i, x, gl)){ return 1; } } } -- tpp; return 0; } void solve(){ read(n); for(int i = 1; i <= n; ++ i){ read(a[i]); if(a[i] == 0){ a0 = i; } } for(int i = 1; i <= n; ++ i){ read(b[i]); if(b[i] == 0){ b0 = i; } } for(int i = 1; i < n; ++ i){ int u, v; read(u, v); g[u].push_back(v); g[v].push_back(u); } dfs1(a0, 0); for(int i = 1; i < tp; ++ i){ swap(a[st[i]], a[st[i+1]]); } bool flg = 1; for(int i = 1; i <= n; ++ i){ if(a[i] != b[i]){ flg = 0; break; } } if(flg){ println(0, tp - 1); return; } dfs2(b0, 0); int p = 0; bool ok = 1; for(int i = 1; i <= n; ++ i){ if(a[i] != b[i]){ nd[i] = 1; } } for(int i = 1; i <= n; ++ i){ if(nd[i]){ if(!p || dep[p] >= dep[i]){ p = fat[i]; } } } nd[p] = 1; for(int i = 1; i <= n; ++ i){ for(int j : g[i]){ if(i < j && nd[i] && nd[j]){ ++ ind[i]; ++ ind[j]; } } } int c = 0; for(int i = 1; i <= n; ++ i){ if(ind[i] == 1){ ++ c; } else if(nd[i] && ind[i] != 2){ ok = 0; break; } } if(c != 2){ ok = 0; } if(!ok){ println(-1); return; } int u = 0, v = 0; for(int i = 1; i <= n; ++ i){ if(ind[i] == 1){ if(u){ v = i; } else { u = i; } } } dfs3(u, 0, v); int psp = 0; for(int i = 1, q = 0; i <= tpp; ++ i){ if(stt[i] != p){ as[++q] = a[stt[i]]; bs[q] = b[stt[i]]; } else { psp = i; } } int dy = 0; for(int i = 1; i < tpp; ++ i){ if(as[1] == bs[i]){ dy = i; for(int j = 1, k = i; j < tpp; ++ j, ++ k){ if(k == tpp){ k = 1; } if(as[j] != bs[k]){ ok = 0; break; } } break; } } if(dy == 0){ ok = 0; } if(!ok){ println(-1); return; } int tmp = p; while(tmp != b0){ so[fat[tmp]] = tmp; tmp = fat[tmp]; } ll ac = 2, rc = 2; ac += tp - 1 + (dep[p] - 1) * 2; rc += tp - 1 + (dep[p] - 1) * 2; ac += (dy - 1) * 1ll * tpp; rc += (tpp - dy) * 1ll * tpp; int np = b0, nq = b0, fl = tp, pr = 0; while(true){ if(np == nq){ -- rc; -- rc; } if(np == a0 || nq == stt[tpp]){ break; } -- fl; np = st[fl]; if(nq == p){ pr = psp; } if(!pr){ nq = so[nq]; } else { ++ pr; nq = stt[pr]; } } np = b0, nq = b0, fl = tp, pr = 0; while(true){ if(np == nq){ -- ac; -- ac; } if(np == a0 || nq == stt[1]){ break; } -- fl; np = st[fl]; if(nq == p){ pr = psp; } if(!pr){ nq = so[nq]; } else { -- pr; nq = stt[pr]; } } println(u, v, min(ac, rc)); }
9. COCI2021-2022#2 - Osumnjičeni [省选/NOI-]
双指针+线段树可以求出对于每个人,最右边能选到哪个人。然后倍增即可。
点击查看代码
const int N = 2e5 + 10, M = 4e5 + 10; int n, q, l[N], r[N], pl[M], tp, t[M*4], tag[M*4], f[N][20]; void psd(int p){ if(tag[p] == -1){ return; } t[p<<1] = t[p<<1|1] = tag[p<<1] = tag[p<<1|1] = exchange(tag[p], -1); } void add(int p, int l, int r, int ql, int qr, int v){ if(qr < l || r < ql){ return; } else if(ql <= l && r <= qr){ t[p] = tag[p] = v; } else { int mid = l + r >> 1; psd(p); add(p<<1, l, mid, ql, qr, v); add(p<<1|1, mid+1, r, ql, qr, v); t[p] = max(t[p<<1], t[p<<1|1]); } } int qry(int p, int l, int r, int ql, int qr){ if(qr < l || r < ql){ return 0; } else if(ql <= l && r <= qr){ return t[p]; } else { int mid = l + r >> 1; psd(p); return max(qry(p<<1, l, mid, ql, qr), qry(p<<1|1, mid+1, r, ql, qr)); } } void solve(){ read(n); memset(tag, -1, sizeof(tag)); for(int i = 1; i <= n; ++ i){ read(l[i], r[i]); pl[++tp] = l[i]; pl[++tp] = r[i]; } sort(pl + 1, pl + tp + 1); tp = unique(pl + 1, pl + tp + 1) - pl - 1; for(int i = 1; i <= n; ++ i){ l[i] = lower_bound(pl + 1, pl + tp + 1, l[i]) - pl; r[i] = lower_bound(pl + 1, pl + tp + 1, r[i]) - pl; } int L = 1, R = 0; while(R < n && qry(1, 1, tp, l[R+1], r[R+1]) == 0){ add(1, 1, tp, l[R+1], r[R+1], 1); ++ R; } f[1][0] = R + 1; for(L = 2; L <= n; ++ L){ add(1, 1, tp, l[L-1], r[L-1], 0); while(R < n && qry(1, 1, tp, l[R+1], r[R+1]) == 0){ add(1, 1, tp, l[R+1], r[R+1], 1); ++ R; } f[L][0] = R + 1; } f[n+1][0] = n + 1; for(int i = 1; i < 20; ++ i){ for(int j = 1; j <= n + 1; ++ j){ f[j][i] = f[f[j][i-1]][i-1]; } } read(q); while(q--){ int x, y, ans = 0; read(x, y); for(int i = 19; i >= 0; -- i){ if(f[x][i] <= y){ ans += (1 << i); x = f[x][i]; } } println(ans + 1); } }
10. BJOI2014 - 想法 [省选/NOI-]
非常厉害!
发现直接做不好做。我们考虑给每个
点击查看代码
const int N = 2e6 + 10, T = 100; int c[N][2], f[N][T], n, m; double ans[N]; void solve(){ read(n, m); for(int i = m + 1; i <= n; ++ i){ read(c[i][0], c[i][1]); } for(int p = 0; p <= 2; ++ p){ for(int i = 1; i <= m; ++ i){ for(int j = 0; j < T; ++ j){ f[i][j] = rand(); } } for(int i = m + 1; i <= n; ++ i){ for(int j = 0; j < T; ++ j){ f[i][j] = min(f[c[i][0]][j], f[c[i][1]][j]); ans[i] += f[i][j]; } } } for(int i = m + 1; i <= n; ++ i){ println((int)(RAND_MAX / ans[i] * T * 3 - 0.5)); } }
11. CF809E - Surprise me! [*3100]
虚树+推式子。
我们有
proof
稍微变形,
所以原式可以变为:
枚举
设:
等于不好算,换成倍数,设:
反演:
考虑计算
变形,得:
前面的直接处理,后面的可以 dp 的同时在
点击查看代码
const int N = 4e5 + 10; const ll P = 1e9 + 7; int n, a[N], v[N], ps[N]; ll F[N], f[N]; namespace Sieve{ int p[N], v[N], c, phi[N], mu[N]; void clc(int n){ phi[1] = mu[1] = 1; for(int i = 2; i <= n; ++ i){ if(!v[i]){ p[++c] = i; phi[i] = i - 1; mu[i] = P - 1; } for(int j = 1; j <= c && i * p[j] <= n; ++ j){ v[i*p[j]] = 1; if(i % p[j]){ phi[i*p[j]] = phi[i] * phi[p[j]]; mu[i*p[j]] = P - mu[i]; } else { phi[i*p[j]] = phi[i] * p[j]; mu[i*p[j]] = 0; break; } } } } ll inv(ll x){ ll ans = 1, y = P - 2; while(y){ if(y & 1){ ans = ans * x % P; } x = x * x % P; y >>= 1; } return ans; } } namespace VirtualTree{ int dfn[N], dfc, dep[N], st[20][N]; vector<int> g[N]; int get(int x, int y){ return dfn[x] < dfn[y] ? x : y; } void dfs(int x, int fa){ dfn[x] = ++ dfc; st[0][dfn[x]] = fa; dep[x] = dep[fa] + 1; for(int i : g[x]){ if(i != fa){ dfs(i, x); } } } void init(){ dfs(1, 0); for(int i = 1; i < 20; ++ i){ for(int j = 1; j + (1<<i) - 1 <= n; ++ j){ st[i][j] = get(st[i-1][j], st[i-1][j+(1<<i-1)]); } } } int lca(int u, int v){ if(u == v){ return u; } if((u = dfn[u]) > (v = dfn[v])){ swap(u, v); } int d = 31 ^ __builtin_clz(v - u ++); return get(st[d][u], st[d][v-(1<<d)+1]); } int h[N], m, a[N], len, val[N]; vector<pair<int, int> > G[N]; set<int> s; int DEP[N]; bool cmp(int x, int y){ return dfn[x] < dfn[y]; } void bld(){ h[++m] = 1; sort(h + 1, h + m + 1, cmp); len = 0; bool is1 = 0; for(int i = 1; i < m; ++ i){ a[++len] = h[i]; a[++len] = lca(h[i], h[i+1]); } for(int i = 2; i <= m; ++ i){ val[h[i]] = v[h[i]]; } a[++len] = h[m]; sort(a + 1, a + len + 1, cmp); len = unique(a + 1, a + len + 1) - a - 1; for(int i = 1; i < len; ++ i){ int lc = lca(a[i], a[i+1]); G[lc].emplace_back(a[i+1], dep[a[i+1]] - dep[lc]); s.insert(lc); s.insert(a[i]); } s.insert(a[len]); } ll siz[N], x, y, z; void clr(){ for(int i : s){ val[i] = siz[i] = DEP[i] = 0; vector<pair<int, int> > ().swap(G[i]); } s.clear(); m = len = 0; } void dfss(int x, int fa){ for(auto i : G[x]){ int y = i.first; DEP[y] = DEP[x] + i.second; dfss(y, x); z = (z + siz[x] * siz[y] % P * DEP[x]) % P; siz[x] += siz[y]; } z = z + (siz[x] * val[x] % P * DEP[x]) % P; siz[x] = (siz[x] + val[x]) % P; } ll solve(){ bld(); x = 0, y = 0, z = 0; dfss(1, 0); z = z * 2 % P; for(int i = 1; i <= len; ++ i){ x = (x + (ll)val[a[i]] * DEP[a[i]]) % P; y = (y + val[a[i]]) % P; z = (z + (ll)val[a[i]] * val[a[i]] % P * DEP[a[i]]) % P; } clr(); return ((2 * x * y - 2 * z) % P + P) % P; } } void solve(){ read(n); Sieve::clc(n); for(int i = 1; i <= n; ++ i){ read(a[i]); v[i] = Sieve::phi[a[i]]; ps[a[i]] = i; } for(int i = 1; i < n; ++ i){ int u, v; read(u, v); VirtualTree::g[u].push_back(v); VirtualTree::g[v].push_back(u); } VirtualTree::init(); for(int x = 1; x <= n; ++ x){ VirtualTree::m = 0; for(int i = x; i <= n; i += x){ VirtualTree::h[++VirtualTree::m] = ps[i]; } F[x] = VirtualTree::solve(); } for(int x = 1; x <= n; ++ x){ for(int d = x; d <= n; d += x){ f[x] = (f[x] + F[d] * Sieve::mu[d/x]) % P; } } ll ans = 0; for(int d = 1; d <= n; ++ d){ ans = (ans + d * Sieve::inv(Sieve::phi[d]) % P * f[d]) % P; } println(ans * Sieve::inv((ll)n * (n-1) % P) % P); }
12. NEERC2017 - Journey from Petersburg to Moscow [省选/NOI-]
发现
对于枚举的
考虑为什么是对的:
- 对于第
大权值正好是 的路径,显然最后算到的答案相同; - 对于第
大权值大于 的路径,答案会多加上 这一部分边的权值与 差值的和,更大; - 对于第
大权值小于 的路径,答案会多加上 这一部分边的权值与 差值的和,更大。
故一定能找到最小解。
点击查看代码
#define int long long const int N = 3010; int n, m, k, pl[N], vs[N], U[N], V[N], W[N]; vector<pair<int, int> > g[N]; ll ds[N]; ll dij(){ priority_queue<pair<int, int> > q; memset(ds, 0x3f, sizeof(ds)); memset(vs, 0, sizeof(vs)); ds[1] = 0; q.push(make_pair(0, 1)); while(!q.empty()){ int x = q.top().second; q.pop(); if(vs[x]){ continue; } vs[x] = 1; for(auto i : g[x]){ int y = i.first, z = i.second; if(ds[y] > ds[x] + z){ ds[y] = ds[x] + z; q.push(make_pair(-ds[y], y)); } } } return ds[n]; } void solve(){ read(n, m, k); for(int i = 1; i <= m; ++ i){ read(U[i], V[i], W[i]); pl[i] = W[i]; } sort(pl + 1, pl + m + 1); int tp = unique(pl + 1, pl + m + 1) - pl - 1; ll ans = 1e18; for(int i = 0; i <= tp; ++ i){ for(int j = 1; j <= m; ++ j){ g[U[j]].emplace_back(V[j], max(0ll, W[j] - pl[i])); g[V[j]].emplace_back(U[j], max(0ll, W[j] - pl[i])); } ans = min(ans, dij() + (ll)k * pl[i]); for(int j = 1; j <= n; ++ j){ vector<pair<int, int> > ().swap(g[j]); } } println(ans); }
13. CF575A - Fibonotci [*2700]
直接矩阵快速幂就行了。
点击查看代码
const int N = 5e4 + 10; int n, m; ll s[N], P, k; pair<ll, ll> q[N]; map<ll, ll> mp; struct mat{ ll a, b, c, d; } t[N*4]; mat operator * (mat x, mat y){ mat z; z.a = (x.a * y.a + x.b * y.c) % P; z.b = (x.a * y.b + x.b * y.d) % P; z.c = (x.c * y.a + x.d * y.c) % P; z.d = (x.c * y.b + x.d * y.d) % P; return z; } void bld(int p, int l, int r){ if(l == r){ t[p] = { 0, s[(l+n-1)%n], 1, s[l%n] }; } else { int mid = l + r >> 1; bld(p<<1, l, mid); bld(p<<1|1, mid+1, r); t[p] = t[p<<1] * t[p<<1|1]; } } mat qry(int p, int l, int r, int ql, int qr){ if(qr < l || r < ql){ return { 1, 0, 0, 1 }; } else if(ql <= l && r <= qr){ return t[p]; } else { int mid = l + r >> 1; return qry(p<<1, l, mid, ql, qr) * qry(p<<1|1, mid+1, r, ql, qr); } } mat qp(mat x, ll y){ mat ans = x; -- y; while(y){ if(y & 1){ ans = ans * x; } x = x * x; y >>= 1; } return ans; } void cg(mat &nw, ll l, ll r){ if(l > r){ return; } if(mp[l-1]){ nw = nw * (mat){ 0, mp[l-1], 1, s[l%n] }; ++ l; } if(l > r){ return; } if(l / n == r / n){ nw = nw * qry(1, 0, n-1, l%n, r%n); } else { nw = nw * qry(1, 0, n-1, l%n, n-1); ll L = (l / n + 1) * n, R = (r / n) * n; if(L != R) nw = nw * qp(qry(1, 0, n-1, 0, n-1), (R-L) / n); nw = nw * qry(1, 0, n-1, 0, r%n); } } void solve(){ scanf("%lld%lld%d", &k, &P, &n); if(k == 0){ puts("0"); return; } for(int i = 0; i < n; ++ i){ scanf("%lld", &s[i]); } scanf("%d", &m); int tp = 0; for(int i = 1; i <= m; ++ i){ ll x, y; scanf("%lld%lld", &x, &y); if(x < k){ q[++tp] = make_pair(x, y); mp[x] = y; } } m = tp; sort(q + 1, q + m + 1); bld(1, 0, n-1); mat nw = { 1, 0, 0, 1 }; for(int i = 1; i < n; ++ i){ nw = nw * qry(1, 0, n-1, i%n, i%n); if(i == k - 1){ printf("%lld\n", nw.d); return; } } ll l = n, r, vl; for(int i = 1; i <= m; ++ i){ r = q[i].first, vl = q[i].second; if(r == q[i-1].first) continue; cg(nw, l, r-1); if(mp[r-1]){ nw = nw * (mat){ 0, mp[r-1], 1, vl }; } else { nw = nw * (mat){ 0, s[(r-1)%n], 1, vl }; } l = r + 1; } cg(nw, l, k-1); printf("%lld\n", nw.d % P); return; }
14. CF888G - Xor-MST [*2300]
性质图 MST 考虑 boruvka。问题转化为如何求集合去掉一个子集剩下部分与
点击查看代码
const int N = 2e5 + 10; int n, a[N], fa[N]; int ch[N*34][2], siz[N*34], tot, ed[N*34]; vector<int> son[N]; void ins(int x, int vl){ int p = 0; for(int i = 30; i >= 0; -- i){ if(!ch[p][(a[x]>>i)&1]){ ch[p][(a[x]>>i)&1] = ++ tot; } p = ch[p][(a[x]>>i)&1]; siz[p] += vl; } ed[p] = x; } int fnd(int x){ int p = 0; for(int i = 30; i >= 0; -- i){ if(siz[ch[p][(x>>i)&1]]){ p = ch[p][(x>>i)&1]; } else { p = ch[p][1-((x>>i)&1)]; } } return ed[p]; } int gf(int x){ return x == fa[x] ? x : gf(fa[x]); } void mg(int x, int y){ x = gf(x); y = gf(y); if(son[x].size() < son[y].size()){ swap(x, y); } fa[y] = x; for(int i : son[y]){ son[x].push_back(i); } } void solve(){ read(n); for(int i = 1; i <= n; ++ i){ read(a[i]); } sort(a + 1, a + n + 1); n = unique(a + 1, a + n + 1) - a - 1; for(int i = 1; i <= n; ++ i){ fa[i] = i; ins(i, 1); son[i].push_back(i); } int cnt = n; ll ans = 0; while(cnt > 1){ static int gx[N], gy[N]; for(int j = 1; j <= n; ++ j){ if(j == gf(j)){ int vl = 2e9; for(int k : son[j]){ ins(k, -1); } for(int k : son[j]){ int p = fnd(a[k]); if(vl > (a[p] ^ a[k])){ vl = a[p] ^ a[k]; gx[j] = k; gy[j] = p; } } for(int k : son[j]){ ins(k, 1); } } } for(int j = 1; j <= n; ++ j){ if(gf(j) == j && gf(gx[j]) != gf(gy[j])){ mg(gx[j], gy[j]); ans += a[gx[j]] ^ a[gy[j]]; -- cnt; } } } println(ans); }
15. Dynamic Rankings [省选/NOI-]
树状数组套线段树。
树状数组每个节点维护的是一段区间的值,那么我们可以用线段树来维护这段区间。单点加就在树状数组上若干点对应线段树加,区间查就提取出区间,在维护这些区间的线段树上一起查。
点击查看代码
const int N = 2e5 + 10, M = 4e7 + 10; int n, m, a[N], b[N], tp, rt[N]; struct qry{ int op, l, r, k; } q[N]; int t[M], cnt, ls[M], rs[M]; vector<int> ts, qs; void add(int &p, int l, int r, int x, int v){ ++ cnt; t[cnt] = t[p]; ls[cnt] = ls[p]; rs[cnt] = rs[p]; p = cnt; if(l == r){ t[p] += v; } else { int mid = l + r >> 1; if(x <= mid){ add(ls[p], l, mid, x, v); } else { add(rs[p], mid+1, r, x, v); } t[p] = t[ls[p]] + t[rs[p]]; } } int qry(int l, int r, int k){ if(l == r){ return l; } int mid = l + r >> 1, x = 0, p = ts.size(); for(int i = 0; i < p; ++ i){ x += qs[i] * t[ls[ts[i]]]; } if(x >= k){ for(int i = 0; i < p; ++ i){ ts[i] = ls[ts[i]]; } return qry(l, mid, k); } else { for(int i = 0; i < p; ++ i){ ts[i] = rs[ts[i]]; } return qry(mid+1, r, k-x); } } void Add(int x, int v, int op){ while(x <= n){ add(rt[x], 1, tp, v, op); x += x & -x; } } int Qry(int l, int r, int k){ vector<int> ().swap(ts); vector<int> ().swap(qs); while(r){ ts.push_back(rt[r]); qs.push_back(1); r -= r & -r; } -- l; while(l){ ts.push_back(rt[l]); qs.push_back(-1); l -= l & -l; } return qry(1, tp, k); } int main(){ scanf("%d%d", &n, &m); for(int i = 1; i <= n; ++ i){ scanf("%d", &a[i]); b[i] = a[i]; } tp = n; for(int i = 1; i <= m; ++ i){ char s[4]; scanf("%s", s); if(s[0] == 'Q'){ q[i].op = 0; scanf("%d%d%d", &q[i].l, &q[i].r, &q[i].k); } else { q[i].op = 1; scanf("%d%d", &q[i].l, &q[i].r); b[++tp] = q[i].r; } } sort(b + 1, b + tp + 1); tp = unique(b + 1, b + tp + 1) - b - 1; for(int i = 1; i <= n; ++ i){ a[i] = lower_bound(b + 1, b + tp + 1, a[i]) - b; Add(i, a[i], 1); } for(int i = 1; i <= m; ++ i){ if(q[i].op == 1){ Add(q[i].l, a[q[i].l], -1); a[q[i].l] = lower_bound(b + 1, b + tp + 1, q[i].r) - b; Add(q[i].l, a[q[i].l], 1); } else { printf("%d\n", b[Qry(q[i].l, q[i].r, q[i].k)]); } } return 0; }
16. EC Online 2023 (I) - Alice and Bob
考场策略肯定是打表找规律,可以发现如下规律:
三元组
必败,当且仅当 且 的质因数分解中有偶数个 。
知道这个结论后就很好做了。把每个数倒着插入 trie 中即可。
点击查看代码
int T, n, cnt[N], ps[N]; ll a[N], b[N]; inline ll clc(ll x){ return x * (x-1) * (x-2) / 6; } int ch[N*64][2], siz[N*64], tt; inline void ins(ll x){ int p = 0; ++ siz[p]; for(ll i = 0; i <= 62; ++ i){ if(!ch[p][(x>>i)&1]){ ch[p][(x>>i)&1] = ++ tt; } p = ch[p][(x>>i)&1]; ++ siz[p]; } } inline void qry(ll x, ll &ans){ int p = 0, op = 1; ans += siz[p] * op; op *= -1; for(ll i = 0; i <= 62; ++ i){ p = ch[p][(x>>i)&1]; ans += siz[p] * op; op *= -1; } } int main(){ scanf("%d", &T); while(T--){ scanf("%d", &n); ll ans = clc(n); for(int i = 1; i <= n; ++ i){ scanf("%lld", &a[i]); b[i] = a[i]; ins(a[i]); } sort(b + 1, b + n + 1); int m = unique(b + 1, b + n + 1) - b - 1; for(register int i = 1; i <= n; ++ i){ ps[i] = lower_bound(b + 1, b + m + 1, a[i]) - b; ++ cnt[ps[i]]; } for(register int i = 1; i <= n; ++ i){ int p = ps[i]; if(cnt[p]){ ll cc = cnt[p], dd = 0; cnt[p] = 0; qry(a[i], dd); ans -= dd * cc * (cc-1) / 2 + clc(cc); } } printf("%lld\n", ans); for(register int i = 0; i <= tt; ++ i){ ch[i][0] = ch[i][1] = siz[i] = 0; } for(register int i = 0; i <= n; ++ i){ cnt[i] = 0; } tt = 0; } return 0; }
17. CF1615F - LEGOndary Grandmaster [*2800]
将两个串的奇数位取反,发现操作变为了交换相邻位的值。那么我们就可以 dp:设
点击查看代码
const int N = 2010; int T, n; char s[N], t[N]; const ll P = 1e9 + 7; ll f[N][N*2], g[N][N*2]; int main(){ scanf("%d", &T); while(T--){ scanf("%d%s%s", &n, s + 1, t + 1); for(int i = 1; i <= n; ++ i){ memset(f[i], 0, sizeof(f[i])); memset(g[i], 0, sizeof(g[i])); if(s[i] != '?' && (i & 1)){ s[i] = '0' + '1' - s[i]; } if(t[i] != '?' && (i & 1)){ t[i] = '0' + '1' - t[i]; } } f[0][n] = 1; for(int i = 1; i <= n; ++ i){ for(int j = 0; j <= n+n; ++ j){ for(int k = 0; k < 2; ++ k){ for(int l = 0; l < 2; ++ l){ if(s[i] != '?' && s[i] != k + '0'){ continue; } if(t[i] != '?' && t[i] != l + '0'){ continue; } if(j+k-l < 0){ continue; } f[i][j+k-l] = (f[i][j+k-l] + f[i-1][j]) % P; g[i][j+k-l] = (g[i][j+k-l] + g[i-1][j] + f[i-1][j] * abs(j-n)) % P; } } } } printf("%lld\n", g[n][n]); f[0][n] = 0; } return 0; }
18. 湖北省选模拟 2023 - 棋圣 / alphago [省选/NOI-]
考虑答案上界:
- 若原图不是二分图,那么每两个棋子之间都可能距离
。上界是 。 - 否则,只有染色不同的两个棋子之间可能距离
。上界是 。
可以证明只有原图是链的情况达不到上界。
proof
若图中存在环,首先可以将所有棋子移动到环上。环上的所有棋子都可以任意选择一个方向移动一步,就可以将所有棋子聚在一起。
若不存在环且不是链,则存在一个度数
最后是链的情况,发现两个棋子可行的距离与原距离同奇偶且不大于原距离。于是可以设
点击查看代码
const int N = 110; int n, m, k, cl[N], mx, mxdeg; pair<int, int> ch[N]; vector<pair<int, int> > g[N]; void dfs(int x, int c){ cl[x] = c; for(auto i : g[x]){ if(cl[i.first] == -1){ dfs(i.first, 1-c); } } } int main(){ scanf("%d%d%d", &n, &m, &k); for(int i = 1; i <= k; ++ i){ scanf("%d%d", &ch[i].first, &ch[i].second); } for(int i = 1; i <= m; ++ i){ int u, v, w; scanf("%d%d%d", &u, &v, &w); mx = max(mx, w); g[u].emplace_back(v, w); g[v].emplace_back(u, w); } for(int i = 1; i <= n; ++ i){ mxdeg = max(mxdeg, (int)g[i].size()); } memset(cl, -1, sizeof(cl)); dfs(1, 0); if(mxdeg > 2 || m >= n){ bool flg = 1; for(int i = 1; i <= n; ++ i){ for(auto j : g[i]){ if(cl[i] == cl[j.first]){ flg = 0; break; } } } if(flg){ ll x = 0, xx = 0, y = 0, yy = 0; for(int i = 1; i <= k; ++ i){ if(ch[i].second == 0){ if(cl[ch[i].first]){ ++ x; } else { ++ xx; } } else { if(cl[ch[i].first]){ ++ y; } else { ++ yy; } } } printf("%lld\n", (x * yy + xx * y) * mx); } else { ll x = 0, y = 0; for(int i = 1; i <= k; ++ i){ if(ch[i].second == 0){ ++ x; } else { ++ y; } } printf("%lld\n", x * y * mx); } } else { static int nid[N], w[N], sum[N][2], ok[N][N]; memset(ok, 0, sizeof(ok)); memset(sum, 0, sizeof(sum)); int x = 0, ls = 0; for(int i = 1; i <= n; ++ i){ if(g[i].size() == 1){ x = i; } } nid[x] = 1; for(int i = 2; i <= n; ++ i){ for(auto j : g[x]){ if(j.first != ls){ nid[j.first] = i; w[i-1] = j.second; ls = x; x = j.first; break; } } } for(int i = 1; i <= k; ++ i){ ch[i].first = nid[ch[i].first]; } sort(ch + 1, ch + k + 1); for(int i = 1; i <= k; ++ i){ for(int j = i; j <= k; ++ j){ int flg = 1; for(int k = i; k <= j; ++ k){ if((ch[i].first + ch[k].first) & 1){ flg = 0; break; } } ok[i][j] = flg; } } for(int i = 1; i <= k; ++ i){ ++ sum[i][ch[i].second]; sum[i][0] += sum[i-1][0]; sum[i][1] += sum[i-1][1]; } static int f[N][N][N]; for(int i = 1; i <= k; ++ i){ for(int j = 1; j <= i; ++ j){ if(!ok[j][i]){ continue; } for(int p = 1; p <= n; ++ p){ if(i == 1){ f[j][i][p] = 0; } int r = j - 1; for(int l = 1; l <= r; ++ l){ if(!ok[l][r]){ continue; } for(int q = 1; q < p; ++ q){ if((p + q + ch[r].first + ch[j].first) & 1){ continue; } if(p - q > ch[j].first - ch[r].first){ continue; } int val = 0; if(q + 1 == p){ val += (sum[i][1] - sum[j-1][1]) * (sum[r][0] - sum[l-1][0]); val += (sum[i][0] - sum[j-1][0]) * (sum[r][1] - sum[l-1][1]); } f[j][i][p] = max(f[j][i][p], f[l][r][q] + val * w[q]); } } } } } int ans = 0; for(int i = 1; i <= k; ++ i){ for(int j = 1; j <= k; ++ j){ for(int p = 1; p <= n; ++ p){ ans = max(ans, f[i][j][p]); } } } printf("%d\n", ans); } return 0; }
19. AT_abc341_g - Highest Ratio [*2208]
所以从右往左维护
点击查看代码
const int N = 2e5 + 10; int n, st[N], tp, r[N]; typedef long long ll; ll a[N], sum[N]; bool calc(int x, int y, int z){ return (sum[x] - sum[z]) * (y - z) >= (sum[y] - sum[z]) * (x - z); } int main(){ scanf("%d", &n); for(int i = 1; i <= n; ++ i){ scanf("%lld", &a[i]); sum[i] = sum[i-1] + a[i]; } st[++tp] = n; for(int i = n-1; i >= 0; -- i){ while(tp >= 2 && calc(st[tp-1], st[tp], i)){ -- tp; } r[i+1] = st[tp]; st[++tp] = i; } for(int i = 1; i <= n; ++ i){ printf("%.8lf\n", (sum[r[i]] - sum[i-1]) * 1.0 / (r[i] - i + 1)); } return 0; }
20. 湖北省选模拟 2023 - 环山危路 / road [省选/NOI-]
竞赛图最大流。边数真的很多。
考虑转化为最小割,设割完后起点所在点集为
又因为竞赛图性质有
点击查看代码
const int N = 3010; int n, m, ind[N], oud[N], p[N]; char e[N][N]; bool cmp(int x, int y){ return oud[x] - ind[x] < oud[y] - ind[y]; } int main(){ scanf("%d%d", &n, &m); for(int i = 1; i <= n; ++ i){ scanf("%s", e[i] + 1); for(int j = 1; j <= n; ++ j){ if(e[i][j] == '1'){ ++ ind[j]; ++ oud[i]; } } p[i] = i; } sort(p + 1, p + n + 1, cmp); while(m--){ int ed, k, S = 0, T = n, sum = 0, ans = 0; static int s[N], is[N]; memset(is, 0, sizeof(is)); scanf("%d%d", &ed, &k); for(int i = 1; i <= k; ++ i){ scanf("%d", &s[i]); is[s[i]] = 1; ++ S; -- T; sum += oud[s[i]] - ind[s[i]]; } ans = S * T + sum; for(int i = 1; i <= n; ++ i){ if(is[p[i]] || p[i] == ed){ continue; } ++ S; -- T; sum += oud[p[i]] - ind[p[i]]; ans = min(ans, S * T + sum); } printf("%d\n", ans / 2); } return 0; }
21. CF568B - Symmetric and Transitive [*1900]
为什么会有不满足自反性但满足传递性与对称性的二元关系呢?重点在于我们可能没办法对于每个
容易发现没有孤立点的话方案数是贝尔数
点击查看代码
const int N = 4010; const ll P = 1e9 + 7; int n; ll bell[N][N]; void solve(){ read(n); bell[0][0] = 1; for(int i = 1; i <= n; ++ i){ bell[i][0] = bell[i-1][i-1]; for(int j = 1; j <= i; ++ j){ bell[i][j] = (bell[i-1][j-1] + bell[i][j-1]) % P; } } println(bell[n][n-1]); }
22. ARC162F - Montage [*3190]
*3190? *2190!
首先可以钦定
那么就可以 dp:设
点击查看代码
const int N = 410; const ll P = 998244353; int n, m; ll C[N][N], f[N][N], g[N][N]; ll sum[N][N]; int main(){ scanf("%d%d", &n, &m); C[0][0] = 1; if(n < m){ swap(n, m); } for(int i = 1; i <= n; ++ i){ C[i][0] = C[i][i] = 1; for(int j = 1; j < i; ++ j){ C[i][j] = (C[i-1][j] + C[i-1][j-1]) % P; } } for(int i = 1; i <= m; ++ i){ f[1][i] = g[1][i] = 1; } for(int l = 1; l <= m; ++ l){ for(int r = 1; r <= m; ++ r){ sum[l][r] = sum[l-1][r] + sum[l][r-1] - sum[l-1][r-1] + f[l][r] + P; while(sum[l][r] >= P) sum[l][r] -= P; } } for(int i = 2; i <= n; ++ i){ for(int l = 1; l <= m; ++ l){ for(int r = l; r <= m; ++ r){ f[l][r] = sum[l][r] - sum[l][max(0, l-2)] + P; while(f[l][r] >= P) f[l][r] -= P; } } for(int l = 1; l <= m; ++ l){ for(int r = 1; r <= m; ++ r){ sum[l][r] = sum[l-1][r] + sum[l][r-1] - sum[l-1][r-1] + f[l][r] + P; while(sum[l][r] >= P) sum[l][r] -= P; } } for(int j = 1; j <= m; ++ j){ for(int p = 1; p <= j; ++ p){ g[i][j] = g[i][j] + f[p][j]; while(g[i][j] >= P) g[i][j] -= P; } } } ll ans = 1; for(int i = 1; i <= n; ++ i){ for(int j = 1; j <= m; ++ j){ ans = (ans + C[n][i] * C[m][j] % P * g[i][j]) % P; } } printf("%lld\n", ans); return 0; }
23. AGC046F - Forbidden Tournament [*3854]
引理 1. 竞赛图缩点后形成一条链。
引理 2. 竞赛图的任意一个点数
的 SCC 中存在三元环 。
两个引理都是竞赛图的基本性质。
对这个竞赛图进行缩点,设链上的 SCC 点集依次为
枚举
考虑任选 SCC 中一点
容易发现
把两条链上的节点按照拓扑序重标号。可以发现对于
可以发现
若
若
- 若
,则 构成子图。 - 若
,则 构成子图。
所以就可以 dp。设
注意一个状态
或 。 ,即 的入度 。 ,即 的入度 。
最后设
现在求出
点击查看代码
const int N = 210; ll P, C[N][N], fac[N], ans, f[N][N]; int n, m; ll calc(int n, int m){ ll res = 0; for(int p = 1; p < n - 1; ++ p){ int q = n - 1 - p; ll sum = 0; f[1][q] = 0; if(p > m || q > m){ continue; } for(int i = 0; i < q; ++ i){ f[1][i] = 1; if(q-i > m || (i && p+i > m)){ f[1][i] = 0; } } for(int i = 2; i <= p; ++ i){ ll pr = 0; for(int j = 0; j <= q; ++ j){ pr = (pr + f[i-1][j]) % P; f[i][j] = pr; if(i-1+q-j > m || (j && p-i+1+j > m)){ f[i][j] = 0; } } } for(int i = 0; i <= q; ++ i){ sum = (sum + f[p][i]) % P; } res = (res + C[n-1][p] * fac[p] % P * fac[q] % P * sum) % P; } return res; } int main(){ scanf("%d%d%lld", &n, &m, &P); fac[0] = C[0][0] = 1; for(int i = 1; i <= n; ++ i){ fac[i] = fac[i-1] * i % P; C[i][0] = C[i][i] = 1; for(int j = 1; j < i; ++ j){ C[i][j] = (C[i-1][j-1] + C[i-1][j]) % P; } } if(m == n - 1){ ans = fac[n]; } for(int i = 0; i <= min(m, n - 3); ++ i){ ans = (ans + fac[i] * C[n][i] % P * calc(n-i, m-i)) % P; } printf("%lld\n", ans); return 0; }
24. BJOI2014 - 大融合 [省选/NOI-]
首先离线,把树建出来。若不连通在最后补边使得连通。
然后遍历操作:
-
若为添加操作
,不妨 是 的父亲,则使用并查集维护 到最浅的一个节点 使得 的路径都是添加过的。然后用树剖将这些点的 都加上 。 -
若为查询操作
,不妨 是 的父亲,则两部分大小分别为 。
点击查看代码
const int N = 2e5 + 10; int n, q, fa[N], dep[N], siz[N], anc[N], sz[N], son[N], dfn[N], top[N]; tuple<int, int, int> op[N]; vector<int> g[N]; int gf(int x){ return x == fa[x] ? x : fa[x] = gf(fa[x]); } void dfs(int x, int fat){ sz[x] = 1; dep[x] = dep[fat] + 1; anc[x] = fat; for(int i : g[x]){ if(i == fat){ continue; } dfs(i, x); sz[x] += sz[i]; if(sz[i] > sz[son[x]]){ son[x] = i; } } } void dfss(int x, int tp){ dfn[x] = ++ *dfn; top[x] = tp; if(son[x]){ dfss(son[x], tp); } for(int i : g[x]){ if(i != anc[x] && i != son[x]){ dfss(i, i); } } } ll t[N*4], tag[N*4]; void psd(int p, int l, int r){ int mid = l + r >> 1; t[p<<1] += tag[p] * (mid - l + 1); t[p<<1|1] += tag[p] * (r - mid); tag[p<<1] += tag[p]; tag[p<<1|1] += tag[p]; tag[p] = 0; } void add(int p, int l, int r, int ql, int qr, ll v){ if(qr < l || r < ql){ return; } else if(ql <= l && r <= qr){ t[p] += v * (r - l + 1); tag[p] += v; } else { int mid = l + r >> 1; psd(p, l, r); add(p<<1, l, mid, ql, qr, v); add(p<<1|1, mid+1, r, ql, qr, v); t[p] = t[p<<1] + t[p<<1|1]; } } ll qry(int p, int l, int r, int x){ if(l == r){ return t[p]; } else { int mid = l + r >> 1; psd(p, l, r); if(x <= mid){ return qry(p<<1, l, mid, x); } else { return qry(p<<1|1, mid+1, r, x); } } } void add(int x, int y, ll v){ while(top[x] != top[y]){ if(dep[top[x]] < dep[top[y]]){ swap(x, y); } add(1, 1, n, dfn[top[x]], dfn[x], v); x = anc[top[x]]; } if(dep[x] > dep[y]){ swap(x, y); } add(1, 1, n, dfn[x], dfn[y], v); } int main(){ scanf("%d%d", &n, &q); for(int i = 1; i <= n; ++ i){ fa[i] = i; } for(int i = 1; i <= q; ++ i){ char ch[5]; int x, y; scanf("%s%d%d", ch, &x, &y); op[i] = { (ch[0] == 'A' ? 0 : 1), x, y }; if(ch[0] == 'A'){ g[x].push_back(y); g[y].push_back(x); fa[gf(x)] = gf(y); } } for(int i = 1; i <= n; ++ i){ if(i == gf(i) && gf(i) != gf(1)){ op[++q] = { 0, 1, i }; g[1].push_back(i); g[i].push_back(1); } } dfs(1, 0); dfss(1, 1); for(int i = 1; i <= n; ++ i){ fa[i] = i; } add(1, 1, n, 1, n, 1); for(int i = 1; i <= q; ++ i){ int x = get<1>(op[i]), y = get<2>(op[i]); if(dep[x] > dep[y]){ swap(x, y); } if(get<0>(op[i]) == 0){ int tp = gf(x); int sy = qry(1, 1, n, dfn[y]); add(tp, x, sy); fa[y] = tp; } else { int tp = gf(x); int all = qry(1, 1, n, dfn[tp]); int sy = qry(1, 1, n, dfn[y]); printf("%lld\n", (ll)(all - sy) * sy); } } return 0; }
25. ARC162E - Strange Constraints [*2780]
考虑 dp 顺序:按数出现在
设
发现随着
其中左边组合数表示从
分析复杂度:看似是
所以总共的循环次数大约为
点击查看代码
const int N = 510; const ll P = 998244353; int n, a[N], pr[N]; ll C[N][N], fac[N], inv[N], f[N][N][N]; ll qp(ll x, ll y){ ll ans = 1; while(y){ if(y & 1){ ans = ans * x % P; } x = x * x % P; y >>= 1; } return ans; } void solve(){ read(n); for(int i = 1; i <= n; ++ i){ read(a[i]); } fac[0] = C[0][0] = inv[0] = 1; for(int i = 1; i <= n; ++ i){ fac[i] = fac[i-1] * i % P; inv[i] = qp(fac[i], P-2); C[i][0] = C[i][i] = 1; for(int j = 1; j < i; ++ j){ C[i][j] = (C[i-1][j] + C[i-1][j-1]) % P; } for(int j = 1; j <= n; ++ j){ if(a[j] >= i){ ++ pr[i]; } } } f[n+1][0][0] = 1; for(int i = n; i >= 1; -- i){ for(int j = 0; i * j <= n && j <= pr[i]; ++ j){ for(int k = 0; k <= pr[i]; ++ k){ if(!f[i+1][j][k]){ continue; } ll pw = 1; for(int x = 0; k + x * i <= pr[i] && j + x <= n; ++ x){ ll fz = C[pr[i]-j][x] * fac[pr[i]-k] % P; ll fm = pw * inv[pr[i]-k-x*i] % P; (f[i][j+x][k+i*x] += f[i+1][j][k] * fz % P * fm) %= P; pw = pw * inv[i] % P; } } } } ll ans = 0; for(int i = 0; i <= n; ++ i){ (ans += f[1][i][n]) %= P; } println(ans); }
26. ARC112F - Die Siedler [*3432]
观察到,若可以进行操作
一个状态对应的第
所以最终可以到达的状态
设
这时候我们有两种方法:
- 暴力枚举
,计算 最小值。复杂度 。 - 同余最短路,建
个点,每个点 表示 的最小的 。初始状态 ,因为 不能取到 。连边 ,边权为 ,表示添加一张第 种牌。最后答案取 。复杂度 。
总复杂度
点击查看代码
const int P = 1214827; int n, m, c[20], s[55][20], d[P+10]; ll vl[20], fac[20], pw[20]; ll clc(int x[]){ ll rs = 0; for(int i = 1; i <= n; ++ i){ rs += x[i] * (1ll << i-1) * fac[i-1]; } return rs; } int cnt(ll x){ int ans = 0; for(int i = 1; i <= n; ++ i){ ans += x % (i + i); x /= i + i; } return ans; } int main(){ scanf("%d%d", &n, &m); fac[0] = 1; for(int i = 1; i <= n; ++ i){ scanf("%d", &c[i]); fac[i] = fac[i-1] * i; } ll st = clc(c), mx = (1ll << n) * fac[n] - 1; ll g = mx; for(int i = 1; i <= m; ++ i){ for(int j = 1; j <= n; ++ j){ scanf("%d", &s[i][j]); } vl[i] = clc(s[i]); g = __gcd(g, vl[i]); } if(g >= P){ int ans = 1e9; ll m = st % g == 0 ? g : st % g; for(ll i = m; i <= mx; i += g){ ans = min(ans, cnt(i)); } printf("%d\n", ans); } else { memset(d, -1, sizeof(d)); queue<int> q; for(int i = 0; i <= n; ++ i){ d[fac[i]*(1ll<<i)%g] = 1; q.push(fac[i]*(1ll<<i)%g); } while(q.size()){ int x = q.front(); q.pop(); for(int i = 0; i <= n; ++ i){ int y = (x + fac[i] * (1ll << i)) % g; if(d[y] == -1){ d[y] = d[x] + 1; q.push(y); } } } printf("%d\n", d[st%g]); } return 0; }
27. ARC112E - Cigar Box [*2659]
考虑移动过的数集合
所以枚举
我们将
点击查看代码
const int N = 3010; typedef long long ll; const ll P = 998244353; int n, m, a[N], pr[N]; ll C[N][N], S[N][N], pw[N]; int main(){ scanf("%d%d", &n, &m); a[0] = n+1; C[0][0] = S[0][0] = pw[0] = 1; for(int i = 1; i <= n; ++ i){ scanf("%d", &a[i]); if(a[i] > a[i-1]){ pr[i] = pr[i-1]; } else { pr[i] = i; } } for(int i = 1; i <= m; ++ i){ C[i][0] = 1; S[i][0] = 0; pw[i] = pw[i-1] * 2 % P; for(int j = 1; j <= i; ++ j){ S[i][j] = (S[i-1][j-1] + S[i-1][j] * j) % P; C[i][j] = (C[i-1][j] + C[i-1][j-1]) % P; } } ll ans = 0; for(int L = 0; L <= n; ++ L){ for(int R = 0; L + R <= min(n, m); ++ R){ if(pr[n-R] <= L + 1){ ans = (ans + S[m][L+R] * C[L+R][R] % P * pw[m-L-R]) % P; } } } printf("%lld\n", ans); return 0; }
28. CF1924D - Balanced Subsequences [*2700]
题意:求所有
有一个结论是只要
考虑把 (
写成向量 )
写成向量
最低点纵坐标为
考虑折线与
所以答案为
another problem
给
考虑递推,设
这个递推只适用于
点击查看代码
const ll P = 1e9 + 7; const int N = 5e3 + 10; int n; ll c, fac[N], inv[N], f[N]; ll qp (ll x, ll y) { ll ans = 1; while (y) { if (y & 1) { ans = ans * x % P; } x = x * x % P; y >>= 1; } return ans; } ll C (int x, int y) { return fac[x] * inv[y] % P * inv[x-y] % P; } int main () { n = 5000; fac[0] = 1; for (int i = 1; i <= n; ++ i) { fac[i] = fac[i-1] * i % P; } inv[n] = qp (fac[n], P-2); for (int i = n - 1; i >= 0; -- i) { inv[i] = inv[i+1] * (i + 1) % P; } int t, n, m, k; scanf("%d", &t); while(t--){ scanf("%d%d%d", &n, &m, &k); if(k > min(n, m)){ puts("0"); continue; } printf("%lld\n", (C(n+m, k) - C(n+m, k-1) + P) % P); } return 0; }
29. AT_dwacon5th_prelims_e - Cyclic GCDs [*2978]
将数组从小到大排序。设
考虑生成函数
有结论:设
proof
考虑
所以
点击查看代码
int n, a[100010]; const ll P = 998244353; void solve(){ int ans = 1; read (n); for (int i = 1; i <= n; ++ i) { read (a[i]); } sort (a + 1, a + n + 1); for (int i = 1; i <= n; ++ i) { ans = (ll)ans * __gcd (i - 1, a[i]) % P; } println (ans); }
30. JSOI2011 - 柠檬 [省选/NOI-]
可以发现对于最优方案,每个选择的区间两边端点颜色相同。所以 设
拆开:
可以把每个颜色分开来斜率优化。每个点的坐标为
- 由于斜率
变小,所以维护下凸; - 由于要取下凸最大值,所以答案取栈顶。
所以维护若干单调栈:
- 弹出栈顶若干元素并插入
; - 弹出栈顶若干元素,使得栈顶转移最优。
点击查看代码
const int N = 1e5 + 10; int n, ls[N]; ll f[N], a[N], cnt[N]; vector<int> g[N]; ll X(int x){ return 2 * a[x] * cnt[x]; } ll Y(int x){ return f[x-1] + a[x] * cnt[x] * cnt[x]; } int main(){ scanf("%d", &n); for(int i = 1; i <= n; ++ i){ scanf("%d", &a[i]); vector<int> &nw = g[a[i]]; int p = nw.size(); cnt[i] = cnt[ls[a[i]]] + 1; ls[a[i]] = i; int k = nw.size(); while(p >= 2 && (Y(i)-Y(nw[p-1])) * (X(nw[p-1]) - X(nw[p-2])) >= (Y(nw[p-1])-Y(nw[p-2])) * (X(i) - X(nw[p-1]))){ nw.pop_back(); -- p; } nw.push_back(i); ++ p; while(p >= 2 && Y(nw[p-1]) - (cnt[i] + 1) * X(nw[p-1]) <= Y(nw[p-2]) - (cnt[i] + 1) * X(nw[p-2])){ nw.pop_back(); -- p; } int j = nw[p-1]; f[i] = Y(j) - (cnt[i] + 1) * X(j) + a[i] * (cnt[i] + 1) * (cnt[i] + 1); } printf("%lld\n", f[n]); return 0; }
31. 省选联考 2022 - 卡牌 [省选/NOI-]
由于值域只有
简单的 dp 是设
由于
容斥系数为
点击查看代码
const int N = 2010; const ll P = 998244353; int n, val[N], m, p[N], pr, pid[N], qid[N], v[300]; vector<pair<int, int> > G[N]; int f[300][1<<14], g[1<<14], pw2[1000010]; int main(){ n = 1e6; pw2[0] = 1; for(int i = 1; i <= n; ++ i){ pw2[i] = pw2[i-1] * 2 % P; } scanf("%d", &n); for(int i = 1, a; i <= n; ++ i){ scanf("%d", &a); ++ val[a]; } pid[1] = p[1] = p[0] = 1; for(int i = 2; i <= 2000; ++ i){ bool flg = 1; for(int j = 2; j * j <= i; ++ j){ if(i % j == 0){ flg = 0; break; } } if(flg){ qid[i] = *p - 1; p[++*p] = i; if(i == 43){ pr = *p; } else if(i > 43){ pid[i] = *p - pr + 1; } } } for(int i = 1; i <= 2000; ++ i){ int x = i, st = 0; for(int j = 2; j <= pr; ++ j){ while(x % p[j] == 0){ x /= p[j]; st |= (1 << j-2); } } if(val[i]){ G[pid[x]].emplace_back(st, val[i]); } } for(int i = 1; i <= 290; ++ i){ f[i][0] = 1; for(auto j : G[i]){ int x = j.first, v = j.second; memcpy(g, f[i], sizeof(g)); for(int k = 0; k < (1 << 14); ++ k){ f[i][k|x] = (f[i][k|x] + (ll)g[k] * (pw2[v]-1)) % P; } } for(int j = 0; j < (1 << 14); ++ j){ g[j] = f[i][(1<<14)-1-j]; } for(int j = 0; j < (1 << 14); ++ j){ f[i][j] = g[j]; } for(int j = 0; j < 14; ++ j){ for(int k = 0; k < (1 << 14); ++ k){ if(k & (1 << j)){ f[i][k^(1<<j)] += f[i][k]; if(f[i][k^(1<<j)] >= P){ f[i][k^(1<<j)] -= P; } } } } } scanf("%d", &m); while(m--){ int c, x, st = 0; scanf("%d", &c); memset(v, 0, sizeof(v)); for(int i = 1; i <= c; ++ i){ scanf("%d", &x); if(x <= 43){ st |= (1 << qid[x]); } else { v[pid[x]] = 1; } } ll ans = 0; for(int j = st; j >= 0; j = (j - 1) & st){ #define gg(x) (((x)&1) ? P-1 : 1) ll tmp = gg(__builtin_popcount(j)); for(int i = 1; i <= 290; ++ i){ ll op = v[i] ? P - 1 : 0; tmp = tmp * (f[i][j] + op) % P; } ans += tmp; if(ans >= P){ ans -= P; } if(!j){ break; } } printf("%lld\n", ans); } return 0; }
32. BJWC2018 - 最长上升子序列 [省选/NOI-]
所有 LIS 长度为
勾长公式:
点击查看代码
const ll P = 998244353; int a[30], n; ll ans, fac = 1; ll qp(ll x, ll y){ ll ans = 1; while(y){ if(y & 1){ ans = ans * x % P; } x = x * x % P; y >>= 1; } return ans; } void dfs(int x, int mx, int dep){ if(x == n){ ll mul = 1; for(int i = 1; i <= dep; ++ i){ for(int j = 1; j <= a[i]; ++ j){ int sum = a[i] - j; for(int k = i; k <= dep && a[k] >= j; ++ k){ ++ sum; } mul = mul * sum % P; } } ans = (ans + qp(fac * qp(mul, P-2) % P, 2) * a[1] % P) % P; } else { for(int j = 1; j <= mx && j + x <= n; ++ j){ a[dep+1] = j; dfs(x+j, j, dep+1); } } } void solve(){ read(n); for(int i = 1; i <= n; ++ i){ fac = fac * i % P; } dfs(0, n, 0); println(ans * qp(fac, P-2) % P); }
33. POI201 - Lightning Conductor [省选/NOI-]
首先只处理左半部分,设
把这些函数图像画出来得:
发现最优的函数一定会是反超之前的所有函数,后来又被新出现的函数反超。
所以维护一个单调队列,其中相邻两个函数的交点横坐标递增。插入一个函数的时候求出它与队首函数的交点,然后把交点横坐标大于这个交点的队内函数出队。
转移的时候查看队首的若干个交点,把交点横坐标小于
点击查看代码
const int N = 5e5 + 10; int n, a[N], p[N], q[N]; double sq[N]; double fx(int p, int x){ return a[p] + sq[x-p]; } int crs(int x, int y){ int l = y, r = n + 1; while(l < r){ int mid = l + r >> 1; if(fx(x, mid) > fx(y, mid)){ l = mid + 1; } else { r = mid; } } return l; } int main(){ scanf("%d", &n); for(int i = 1; i <= n; ++ i){ sq[i] = sqrt(i); scanf("%d", &a[i]); } int l = 1, r = 0; for(int i = 1; i <= n; ++ i){ while(l < r && crs(q[r-1], q[r]) >= crs(q[r], i)){ -- r; } q[++r] = i; while(l < r && crs(q[l], q[l+1]) <= i){ ++ l; } p[i] = ceil(fx(q[l], i)) - a[i]; } l = 1, r = 0; reverse(a + 1, a + n + 1); reverse(p + 1, p + n + 1); for(int i = 1; i <= n; ++ i){ while(l < r && crs(q[r-1], q[r]) >= crs(q[r], i)){ -- r; } q[++r] = i; while(l < r && crs(q[l], q[l+1]) <= i){ ++ l; } p[i] = max(p[i], (int)ceil(fx(q[l], i)) - a[i]); } reverse(p + 1, p + n + 1); for(int i = 1; i <= n; ++ i){ printf("%d\n", p[i]); } return 0; }
34. JLOI2015 - 战争调度 [省选/NOI-]
首先有一个暴力的做法:从左往右扫所有的自根向叶的链上选择的状态
还有一个做法是:dfs 遍历每个点的同时记录根到点路径的选择方案,遍历到叶子的时候根据选择方案算出叶子的答案,回溯的时候使用 dp:
这两个做法看起来很相似,但是为什么复杂度不同?或者说第一个做法多计算了哪些部分?
实际上,对于根的两棵子树
点击查看代码
const int N = 1030; int n, m, zz[N][13], hq[N][13], f[N][N]; void dfs(int dep, int x, int state){ if(dep == n){ int ZZ = 0, HQ = 0, p = x - (1 << n - 1) + 1; for(int i = 1; i < n; ++ i){ if(state & (1 << i)){ ZZ += zz[p][n-i]; } else { HQ += hq[p][n-i]; } } f[x][0] = HQ; f[x][1] = ZZ; } else { int siz = 1 << (n - dep - 1); for(int i = 0; i <= siz * 2; ++ i){ f[x][i] = 0; } dfs(dep + 1, x << 1, state); dfs(dep + 1, x << 1 | 1, state); for(int i = 0; i <= siz; ++ i){ for(int j = 0; j <= siz; ++ j){ f[x][i+j] = max(f[x][i+j], f[x<<1][i] + f[x<<1|1][j]); } } dfs(dep + 1, x << 1, state | (1 << dep)); dfs(dep + 1, x << 1 | 1, state | (1 << dep)); for(int i = 0; i <= siz; ++ i){ for(int j = 0; j <= siz; ++ j){ f[x][i+j] = max(f[x][i+j], f[x<<1][i] + f[x<<1|1][j]); } } } } void solve(){ read(n, m); for(int i = 1; i <= (1 << n-1); ++ i){ for(int j = 1; j < n; ++ j){ read(zz[i][j]); } } for(int i = 1; i <= (1 << n-1); ++ i){ for(int j = 1; j < n; ++ j){ read(hq[i][j]); } } dfs(1, 1, 0); int ans = 0; for(int i = 0; i <= m; ++ i){ ans = max(ans, f[1][i]); } println(ans); }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步