ZJOI 2019 简要题解
从这里开始
感觉就没几个题能写,不过暴力分确实给的很多。每日一吹 scoi 2019
Round 1
Problem A 麻将
考虑怎么判断,先判断有没有超过 $7$ 种大小大于等于 2 ,然后依次考虑每种大小,设 $f_{i, j, 0/1}$ 表示前一种和前面第 2 种分别留下了多少个用于凑面子,以及有没有对子的情况下最多能凑出多少面子。注意到 $j \geqslant i$,所以只用记录 $j' = j - i$,又因连续三个留下了至少 3 个,可以变成 $(x, x, x), (x + 1, x + 1, x + 1), (x + 2, x + 2, x+2)$,因此有 $j', i \leqslant 2$。
然后做一些简单标准化然后爆搜发现状态数并不多。注意 dp 值不能超过 4 。
剩下就考虑选出 $i$ 张牌还没有胡的概率。相信这个大家都会。
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; template <typename T> bool vmax(T& a, T b) { return a < b ? (a = b, true) : false; } #define ll long long void exgcd(int a, int b, int& x, int& y) { if (!b) { x = 1, y = 0; } else { exgcd(b, a % b, y, x); y -= (a / b) * x; } } int inv(int a, int n) { int x, y; exgcd(a, n, x, y); return (x < 0) ? (x + n) : (x); } const int Mod = 998244353; template <const int Mod = :: Mod> class Z { public: int v; Z() : v(0) { } Z(int x) : v(x){ } Z(ll x) : v(x % Mod) { } friend Z operator + (const Z& a, const Z& b) { int x; return Z(((x = a.v + b.v) >= Mod) ? (x - Mod) : (x)); } friend Z operator - (const Z& a, const Z& b) { int x; return Z(((x = a.v - b.v) < 0) ? (x + Mod) : (x)); } friend Z operator * (const Z& a, const Z& b) { return Z(a.v * 1ll * b.v); } friend Z operator ~(const Z& a) { return inv(a.v, Mod); } friend Z operator - (const Z& a) { return Z(0) - a; } Z& operator += (Z b) { return *this = *this + b; } Z& operator -= (Z b) { return *this = *this - b; } Z& operator *= (Z b) { return *this = *this * b; } friend boolean operator == (const Z& a, const Z& b) { return a.v == b.v; } }; Z<> qpow(Z<> a, int p) { Z<> rt = Z<>(1), pa = a; for ( ; p; p >>= 1, pa = pa * pa) { if (p & 1) { rt = rt * pa; } } return rt; } typedef Z<> Zi; typedef class Status { public: int f[2][3][3], cnt; void reset() { for (int i = 0; i < 2; i++) { for (int j = 0; j < 3; j++) { for (int k = 0; k < 3; k++) { f[i][j][k] = -142857; } } } cnt = 0; } void standard() { for (int i = 0; i < 2; i++) { for (int j = 0; j < 3; j++) { for (int k = 0; k < 3; k++) { if (f[i][j][k] < 0) { f[i][j][k] = -142857; } f[i][j][k] = min(f[i][j][k], 4); } } } } bool operator < (Status b) const { for (int i = 0; i < 2; i++) { for (int j = 0; j <= 2; j++) { for (int k = 0; k <= 2; k++) { if (f[i][j][k] ^ b.f[i][j][k]) { return f[i][j][k] < b.f[i][j][k]; } } } } return cnt < b.cnt; } bool valid() { if (cnt >= 7) { return false; } for (int j = 0; j <= 2; j++) { for (int k = 0; k <= 2; k++) { if (f[1][j][k] >= 4) { return false; } } } return true; } Status extend(int c) { Status nf; nf.reset(); nf.cnt = cnt + (c >= 2); for (int j = 0; j <= 2; j++) { for (int k = 0; k <= 2; k++) { for (int t = 0; t <= j && t <= c; t++) { int nj = min(k + j - t, min(2, c - t)); int nk = min(2, c - t - nj); vmax(nf.f[0][nj][nk], f[0][j][k] + t); vmax(nf.f[1][nj][nk], f[1][j][k] + t); } if (c >= 3) { for (int t = 0; t <= j && t <= c - 3; t++) { int nj = min(k + j - t, min(2, c - 3 - t)); int nk = c - t - nj - 3; vmax(nf.f[0][nj][nk], f[0][j][k] + t + 1); vmax(nf.f[1][nj][nk], f[1][j][k] + t + 1); } } if (c >= 2) { for (int t = 0; t <= j && t <= c - 2; t++) { int nj = min(k + j - t, min(2, c - t - 2)); int nk = c - t - nj - 2; vmax(nf.f[1][nj][nk], f[0][j][k] + t); } } } } nf.standard(); return nf; } void log() { cerr <<"--- "; cerr << cnt << "\n"; for (int i = 0; i <= 2; i++) { for (int j = 0; j <= 2; j++) { cerr << f[1][i][j] << " "; } cerr << '\n'; } cerr << '\n'; cerr << "----\n"; } } Status; const int S = 4000; const Zi comb[5][5] = {{1}, {1, 1}, {1, 2, 1}, {1, 3, 3, 1}, {1, 4, 6, 4, 1}}; int cnts = 0; map<Status, int> Gs; int tr[S][5]; int dfs(Status s) { if (Gs.count(s)) { return Gs[s]; } int x = cnts++; assert(cnts < S); Gs[s] = x; for (int c = 0; c <= 4; c++) { Status t = s.extend(c); if (t.valid()) { tr[x][c] = dfs(t); } else { tr[x][c] = -1; } } return x; } int n; int cnt[105]; Zi f[2][405][S]; int main() { Status s0; s0.reset(); s0.f[0][0][0] = 0; dfs(s0); scanf("%d", &n); for (int i = 1, x; i <= 13; i++) { scanf("%d%*d", &x); cnt[x]++; } f[0][0][0] = 1; int cur = 0; for (int i = 1; i <= n; i++) { memset(f[cur ^= 1], 0, sizeof(f[0])); for (int j = 0; j <= (i - 1) * 4; j++) { for (int s = 0; s < cnts; s++) { Zi v = f[cur ^ 1][j][s]; if (!v.v) { continue; } int hc = cnt[i]; for (int c = 0; c + hc < 5; c++) { int ns = tr[s][c + hc]; if (~ns) { f[cur][j + c][ns] += v * comb[4 - hc][c]; } } } } } int T = 4 * n - 13; vector<Zi> fac (T + 1); fac[0] = 1; for (int i = 1; i <= T; i++) { fac[i] = fac[i - 1] * i; } Zi ans = 0; for (int i = 0; i < T; i++) { Zi sum = 0; for (int s = 0; s < cnts; s++) { sum += f[cur][i][s]; } ans += sum * fac[i] * fac[T - i]; } ans *= ~fac[T]; printf("%d\n", ans.v); return 0; }
Problem B 线段树
考虑一个点的贡献,问题相当于问有多少操作集合使得它的 $tg$ 为 1.考虑它到根的链上,显然只用记录三种状态:没有点有标记,它没有标记但其中某个点有,它有标记。然后修改相当于单点乘某个矩阵或者区间乘某个矩阵。
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; #define ll long long void exgcd(int a, int b, int& x, int& y) { if (!b) { x = 1, y = 0; } else { exgcd(b, a % b, y, x); y -= (a / b) * x; } } int inv(int a, int n) { int x, y; exgcd(a, n, x, y); return (x < 0) ? (x + n) : (x); } const int Mod = 998244353; template <const int Mod = :: Mod> class Z { public: int v; Z() : v(0) { } Z(int x) : v(x){ } Z(ll x) : v(x % Mod) { } friend Z operator + (const Z& a, const Z& b) { int x; return Z(((x = a.v + b.v) >= Mod) ? (x - Mod) : (x)); } friend Z operator - (const Z& a, const Z& b) { int x; return Z(((x = a.v - b.v) < 0) ? (x + Mod) : (x)); } friend Z operator * (const Z& a, const Z& b) { return Z(a.v * 1ll * b.v); } friend Z operator ~(const Z& a) { return inv(a.v, Mod); } friend Z operator - (const Z& a) { return Z(0) - a; } Z& operator += (Z b) { return *this = *this + b; } Z& operator -= (Z b) { return *this = *this - b; } Z& operator *= (Z b) { return *this = *this * b; } friend boolean operator == (const Z& a, const Z& b) { return a.v == b.v; } }; Z<> qpow(Z<> a, int p) { Z<> rt = Z<>(1), pa = a; for ( ; p; p >>= 1, pa = pa * pa) { if (p & 1) { rt = rt * pa; } } return rt; } typedef Z<> Zi; const int N = 1e5 + 5; typedef class Matrix { public: Zi a[3][3]; Matrix() { memset(a, 0, sizeof(a)); } Matrix operator + (Matrix b) { Matrix rt; #define _(x, y) rt[x][y] = a[x][y] + b[x][y]; _(0, 0) _(0, 1) _(0, 2) _(1, 0) _(1, 1) _(1, 2) _(2, 0) _(2, 1) _(2, 2) #undef _ return rt; } Matrix operator - (Matrix b) { Matrix rt; #define _(x, y) rt[x][y] = a[x][y] - b[x][y]; _(0, 0) _(0, 1) _(0, 2) _(1, 0) _(1, 1) _(1, 2) _(2, 0) _(2, 1) _(2, 2) #undef _ return rt; } Matrix operator * (Matrix b) { Matrix rt; #define _(x, y) rt[x][y] = a[x][0] * b[0][y] + a[x][1] * b[1][y] + a[x][2] * b[2][y]; _(0, 0) _(0, 1) _(0, 2) _(1, 0) _(1, 1) _(1, 2) _(2, 0) _(2, 1) _(2, 2) #undef _ return rt; } Zi* operator [] (int p) { return a[p]; } } Matrix; typedef class SegTreeNode { public: bool has_tg; Matrix v, s, tg; SegTreeNode *l, *r; void upd(Matrix t) { v = v * t; s = s * t; if (has_tg) { tg = tg * t; } else { tg = t; has_tg = true; } } void upd(Matrix t, Matrix t1) { s = (s - v) * t1; v = v * t; s = s + v; if (has_tg) { tg = tg * t1; } else { tg = t1; has_tg = true; } } void _upd(Matrix x) { s = s - v; v = v * x; s = s + v; } void push_down() { if (has_tg) { l->upd(tg); r->upd(tg); has_tg = false; } } void push_up() { s = l->s + v + r->s; } } SegTreeNode; Matrix I, m1, m2, m3, m4, m5; void prepare_matrix() { I[0][0] = I[1][1] = I[2][2] = 1; m1 = m2 = m3 = m4 = I; m1[0][0] += 1, m1[1][0] += 1, m1[2][0] += 1; m2[0][2] += 1, m2[1][2] += 1, m2[2][2] += 1; m3[0][1] += 1, m3[1][1] += 1, m3[2][2] += 1; m4[0][0] += 1, m4[1][2] += 1, m4[2][2] += 1; m5 = I + I; } typedef class SegmentTree { public: static SegTreeNode pool[N << 1]; static SegTreeNode* top; static SegTreeNode* newnode() { top->v[0][0] = 1; top->has_tg = false; return top++; } int n, low; SegTreeNode* rt; SegmentTree() : rt(NULL) { } void build(SegTreeNode*& p, int l, int r) { p = newnode(); if (l ^ r) { int mid = (l + r) >> 1; build(p->l, l, mid); build(p->r, mid + 1, r); p->push_up(); } else { p->s = p->v; } } void build(int n, int low = 1) { this->n = n; this->low = low; build(rt, low, n); } void modify(SegTreeNode* p, int l, int r, int ql, int qr) { if (l == ql && r == qr) { p->upd(m2, m3); return; } int mid = (l + r) >> 1; p->push_down(); p->v = p->v * m1; if (qr <= mid) { modify(p->l, l, mid, ql, qr); p->r->upd(m4, m5); } else if (ql > mid) { modify(p->r, mid + 1, r, ql, qr); p->l->upd(m4, m5); } else { modify(p->l, l, mid, ql, mid); modify(p->r, mid + 1, r, mid + 1, qr); } p->push_up(); } void modify(int l, int r) { assert(l >= low && r <= n); assert(l <= r); modify(rt, low, n, l, r); } Zi query() { return rt->s[0][2]; } } SegmentTree; SegTreeNode SegmentTree :: pool[N << 1]; SegTreeNode* SegmentTree :: top = SegmentTree :: pool; int n, m; SegmentTree st; int main() { prepare_matrix(); scanf("%d%d", &n, &m); st.build(n); int op, x, y; while (m--) { scanf("%d", &op); if (op == 1) { scanf("%d%d", &x, &y); st.modify(x, y); } else { printf("%d\n", st.query().v); } } return 0; }
Problem C Minimax 搜索
假设最终根的权值 $x$ 。首先如果 $S$ 中包含 $x$,显然答案为 1,接下来不再考虑。
否则考虑计算答案 $ > k$ 的方案数,不难发现只用考虑将根的权值变为 $x + 1$ 或者 $x - 1$。假设是变为 $x + 1$,那么将 $\leqslant x$ 中能够修改的都改为 $x + 1$。然后将小于等于 $x$ 的记为 0,大于的记为 1,判断根节点的权值是否是 0。不难发现不能变为 $x + 1$ 和不能变为 $x - 1$ 的情况是独立,只用将它们的方案数乘起来。
然后按某种顺序使得某些点可以修改,然后 ddp 维护一下就行了。
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; #define ll long long void exgcd(int a, int b, int& x, int& y) { if (!b) { x = 1, y = 0; } else { exgcd(b, a % b, y, x); y -= (a / b) * x; } } int inv(int a, int n) { int x, y; exgcd(a, n, x, y); return (x < 0) ? (x + n) : (x); } const int Mod = 998244353; template <const int Mod = :: Mod> class Z { public: int v; Z() : v(0) { } Z(int x) : v(x){ } Z(ll x) : v(x % Mod) { } friend Z operator + (const Z& a, const Z& b) { int x; return Z(((x = a.v + b.v) >= Mod) ? (x - Mod) : (x)); } friend Z operator - (const Z& a, const Z& b) { int x; return Z(((x = a.v - b.v) < 0) ? (x + Mod) : (x)); } friend Z operator * (const Z& a, const Z& b) { return Z(a.v * 1ll * b.v); } friend Z operator ~(const Z& a) { return inv(a.v, Mod); } friend Z operator - (const Z& a) { return Z(0) - a; } Z& operator += (Z b) { return *this = *this + b; } Z& operator -= (Z b) { return *this = *this - b; } Z& operator *= (Z b) { return *this = *this * b; } friend boolean operator == (const Z& a, const Z& b) { return a.v == b.v; } }; Z<> qpow(Z<> a, int p) { Z<> rt = Z<>(1), pa = a; for ( ; p; p >>= 1, pa = pa * pa) { if (p & 1) { rt = rt * pa; } } return rt; } typedef Z<> Zi; const int N = 2e5 + 5; typedef class Matrix { public: Zi a[2][2]; void reset() { memset(a, 0, sizeof(a)); } void setI() { a[0][0] = a[1][1] = 1; } Zi* operator [] (int p) { return a[p]; } Matrix operator * (Matrix b) { Matrix rt; #define G(x, y) rt[x][y] = a[x][0] * b[0][y] + a[x][1] * b[1][y]; G(0, 0) G(0, 1) G(1, 0) G(1, 1); #undef G return rt; } } Matrix; typedef class SegTreeNode { public: Matrix a; SegTreeNode *l, *r, *fa; void push_up() { a = l->a * r->a; } } SegTreeNode; SegTreeNode pool[N << 2]; SegTreeNode *_top; SegTreeNode* newnode() { _top->a.reset(); _top->l = _top->r = _top->fa = NULL; return _top++; } int n, _L, _R; Zi ans[N]; int us[N], vs[N], dep[N]; vector<int> G[N]; int sz[N], zson[N], fa[N]; int dfs(int p, int fa) { ::fa[p] = fa; dep[p] = dep[fa] + 1; if (fa) { G[p].erase(find(G[p].begin(), G[p].end(), fa)); } int rt = (G[p].empty()) ? (p) : ((dep[p] & 1) ? 0 : (n + 1)); sz[p] = 1; for (auto e : G[p]) { int tmp = dfs(e, p); rt = (dep[p] & 1) ? max(rt, tmp) : min(rt, tmp); sz[p] += sz[e]; if (sz[e] > sz[zson[p]]) { zson[p] = e; } } return rt; } SegTreeNode *rt1[N], *rt2[N]; int top[N], bot[N], stk[N], tp; void build(SegTreeNode* &p, int l, int r, SegTreeNode** ar, const vector<int>& v) { if (l > r) { return; } p = newnode(); p->a.setI(); if (l == r) { ar[v[l]] = p; return; } int mid = (l + r) >> 1; build(p->l, l, mid, ar, v); build(p->r, mid + 1, r, ar, v); p->l->fa = p->r->fa = p; } void build() { for (int i = 1; i <= tp; i++) { top[stk[i]] = stk[tp]; bot[stk[i]] = stk[1]; } SegTreeNode* prt; build(prt, 0, tp - 1, rt1, vector<int>(stk + 1, stk + tp + 1)); for (int i = 1; i <= tp; i++) { int p = stk[i]; vector<int> ch = G[p]; if (zson[p]) { ch.erase(find(ch.begin(), ch.end(), zson[p])); } build(prt, 0, (signed) ch.size() - 1, rt2, ch); } tp = 0; } void dfs(int p) { if (!G[p].empty()) { for (auto e : G[p]) { if (e ^ zson[p]) { dfs(e); build(); } } dfs(zson[p]); } stk[++tp] = p; } Matrix pack(int p, Zi f0, Zi f1) { // p is the father Matrix rt; memset(rt.a, 0, sizeof(rt.a)); if (dep[p] & 1) { rt[0][0] = f0, rt[0][1] = f1; rt[1][1] = f1 + f0; } else { rt[0][0] = f0 + f1; rt[1][0] = f0, rt[1][1] = f1; } return rt; } Zi modify(int x, Zi f0, Zi f1, int _ = 0) { Matrix a; memset(a.a, 0, sizeof(a.a)); a[0][0] = f0, a[0][1] = f1; SegTreeNode* p; while (x) { p = rt1[x]; p->a = a; for ( ; p->fa; (p = p->fa)->push_up()); x = top[x]; if (x == 1) { return p->a[0][_]; } a = pack(fa[x], p->a[0][0], p->a[0][1]); p = rt2[x]; p->a = a; for ( ; p->fa; (p = p->fa)->push_up()); a = p->a; x = fa[x]; } assert(false); return a[0][0]; } int xrt, cntleaf = 0; vector<Zi> work1() { vector<int> leaf; Zi rest = 1; for (int i = 1; i <= n; i++) { if (G[i].empty()) { modify(i, (i <= xrt), (i > xrt)); if (i < xrt) { leaf.push_back(i); ++cntleaf; rest = rest + rest; } } } sort(leaf.begin(), leaf.end(), greater<int>()); vector<Zi> f (n); auto it = leaf.begin(), _it = leaf.end(); Zi cf = rest, inv2 = (Mod + 1) >> 1; for (int d = 0; d < n; d++) { while (it != _it && *it >= xrt - d + 1) { cf = modify(*it, 1, 1) * (rest *= inv2); it++; } f[d] = cf; } return f; } vector<Zi> work2() { vector<int> leaf; Zi rest = 1; for (int i = 1; i <= n; i++) { if (G[i].empty()) { modify(i, (i < xrt), (i >= xrt)); if (i > xrt) { leaf.push_back(i); ++cntleaf; rest = rest + rest; } } } sort(leaf.begin(), leaf.end()); vector<Zi> f (n); auto it = leaf.begin(), _it = leaf.end(); Zi cf = rest, inv2 = (Mod + 1) >> 1; for (int d = 0; d < n; d++) { while (it != _it && *it <= xrt + d - 1) { cf = modify(*it, 1, 1, 1) * (rest *= inv2); it++; } f[d] = cf; } return f; } int main() { scanf("%d%d%d", &n, &_L, &_R); for (int i = 1, u, v; i < n; i++) { scanf("%d%d", &u, &v); G[u].push_back(v); G[v].push_back(u); } _top = pool; xrt = dfs(1, 0); dfs(1); build(); auto a = work1(); auto b = work2(); for (int i = 0; i < n; i++) { ans[i + 1] = a[i] * b[i]; } for (int i = 1; i <= n; i++) { ans[i] = ans[i] - ans[i + 1]; } ans[1] += qpow(2, cntleaf); ans[n] -= 1; for (int i = _L; i <= _R; i++) { printf("%d%c", ans[i].v, (i == _R) ? ('\n') : ' '); } return 0; }
Round 2
Problem A 开关
不考虑第一次的话,不难用 egf 表示出经过 $i$ 步从初始状态回到全 0 的生成函数是 $F(x) = 2^{-n}\prod_i [e^{p_ix} + (-1)^{s_i}e^{-p_i x}] $。
设从某个状态经过 $i$ 步回到它本身状态的 egf 是 $G(x) = 2^{-n}\prod_i [e^{p_ix} + (-1)^{s_i}e^{-p_i x}]$ 。
设它们的 ogf 形式分别是 $f(x), g(x)$,设走 $i$ 步第一次回到全 0 的生成函数为 $h(x)$。
显然有 $h(x) g(x) = f(x)$。注意到 $h'(1)$ 即为答案。考虑怎么求 $f, g$,注意到 $F(x) = \sum_t a_t e^{t x}$,这里可以直接把 $e^{t x}$ 换成 $\frac{1}{1 - tx}$ 来得到 ogf。
现在的问题变成计算 $\left ( \frac{f(x)}{g(x)} \right )'\mid_{x = 1}$。你注意到分子分母都包含 $\frac{1}{1 - x}$,分子分母同乘 $(1 - x)$ 再套用求导的公式。 然后手算一下 $\left ( \frac{1 - x}{1 - kx} \right )'\mid_{x = 1} = \frac{1}{k - 1}$。
剩下只用做一个暴力背包就行了。
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; #define ll long long void exgcd(int a, int b, int& x, int& y) { if (!b) { x = 1, y = 0; } else { exgcd(b, a % b, y, x); y -= (a / b) * x; } } int inv(int a, int n) { int x, y; exgcd(a, n, x, y); return (x < 0) ? (x + n) : (x); } const int Mod = 998244353; template <const int Mod = :: Mod> class Z { public: int v; Z() : v(0) { } Z(int x) : v(x){ } Z(ll x) : v(x % Mod) { } friend Z operator + (const Z& a, const Z& b) { int x; return Z(((x = a.v + b.v) >= Mod) ? (x - Mod) : (x)); } friend Z operator - (const Z& a, const Z& b) { int x; return Z(((x = a.v - b.v) < 0) ? (x + Mod) : (x)); } friend Z operator * (const Z& a, const Z& b) { return Z(a.v * 1ll * b.v); } friend Z operator ~(const Z& a) { return inv(a.v, Mod); } friend Z operator - (const Z& a) { return Z(0) - a; } Z& operator += (Z b) { return *this = *this + b; } Z& operator -= (Z b) { return *this = *this - b; } Z& operator *= (Z b) { return *this = *this * b; } friend boolean operator == (const Z& a, const Z& b) { return a.v == b.v; } }; Z<> qpow(Z<> a, int p) { Z<> rt = Z<>(1), pa = a; for ( ; p; p >>= 1, pa = pa * pa) { if (p & 1) { rt = rt * pa; } } return rt; } typedef Z<> Zi; const int N = 105, V = 5e4 + 3; int n, sp; int s[N], p[N]; Zi f[V], g[V]; Zi calc(Zi* f) { Zi ret = 0; for (int i = 0; i < sp; i++) { ret += ~(Zi(i) - sp) * f[i]; } return ret * sp * ((Mod + 1) >> 1); } int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%d", s + i); } for (int i = 1; i <= n; i++) { scanf("%d", p + i); } f[0] = g[0] = 1; for (int i = 1; i <= n; i++) { sp += p[i]; for (int j = sp; j >= p[i]; j--) { g[j] += g[j - p[i]]; f[j] = ((s[i]) ? (-f[j]) : f[j]) + f[j - p[i]]; } if (s[i]) { for (int j = 0; j < p[i]; j++) { f[j] = -f[j]; } } } Zi ans = calc(f) - calc(g); printf("%d\n", ans.v); return 0; }
Problem B 语言
启发式合并维护链并。
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; typedef multiset<int>::iterator iter; const int N = 1e5 + 5, N2 = N << 1; template <typename T> class SparseTable { public: int n; vector<T> Log2; vector<vector<T>> f; function<boolean(T, T)> compare; void init(int n, T* a, const function<boolean(T, T)>& compare = less<T>()) { this->n = n; this->compare = compare; Log2.resize(n + 1); Log2[0] = -1; for (int i = 1; i <= n; i++) { Log2[i] = Log2[i >> 1] + 1; } f.resize(Log2[n] + 1, vector<T>(n + 1)); for (int i = 1; i <= n; i++) { f[0][i] = a[i]; } for (int i = 1; i <= Log2[n]; i++) { for (int j = 1; j + (1 << i) - 1 <= n; j++) { T& x = f[i - 1][j], &y = f[i - 1][j + (1 << (i - 1))]; if (compare(x, y)) { f[i][j] = x; } else { f[i][j] = y; } } } } T query(int l, int r) { int b = Log2[(r - l + 1)]; T& x = f[b][l], &y = f[b][r - (1 << b) + 1]; return compare(x, y) ? x : y; } }; int n, m; int dfc; vector<int> G[N]; SparseTable<int> st; int edep[N2], ein[N2], eout[N2], tour[N2]; int edist(int x, int y) { // euler order if (x > y) { swap(x, y); } return edep[x] + edep[y] - (edep[st.query(x, y)] << 1); } int lca(int x, int y) { x = ein[x], y = eout[y]; if (x > y) { swap(x, y); } return tour[st.query(x, y)]; } void dfs(int p, int fa, int d) { edep[++dfc] = d; tour[dfc] = p; ein[p] = dfc; for (auto e : G[p]) { if (e ^ fa) { dfs(e, p, d + 1); edep[++dfc] = d; tour[dfc] = p; } } eout[p] = dfc; } typedef class VirtualTree { public: int length; multiset<int> P; iter prefix(iter x) { return x == P.begin() ? --P.end() : --x; } iter suffix(iter x) { ++x; return x == P.end() ? P.begin() : x; } void insert(int p) { iter x = P.insert(p); iter pr = prefix(x); iter sf = suffix(x); length -= edist(*pr, *sf); length += edist(*pr, p); length += edist(p, *sf); } void remove(int p) { iter x = P.find(p); iter pr = prefix(x); iter sf = suffix(x); length -= edist(*pr, p); length -= edist(p, *sf); length += edist(*pr, *sf); P.erase(x); } int size() { return P.size(); } } VirtualTree; VirtualTree _vt[N]; VirtualTree *vt[N]; vector<int> Vi[N], Vo[N]; long long ans = 0; void merge(int x, int y) { if (vt[x]->size() < vt[y]->size()) { swap(vt[x], vt[y]); } for (auto p : vt[y]->P) { vt[x]->insert(p); } } void solve(int p, int fa) { for (auto e : G[p]) { if (e ^ fa) { solve(e, p); merge(p, e); } } for (auto x : Vi[p]) { vt[p]->insert(x); } ans += vt[p]->length; for (auto x : Vo[p]) { vt[p]->remove(x); vt[p]->remove(x); } } int main() { scanf("%d%d", &n, &m); for (int i = 1, u, v; i < n; i++) { scanf("%d%d", &u, &v); G[u].push_back(v); G[v].push_back(u); } for (int i = 1; i <= n; i++) { vt[i] = _vt + i; } dfs(1, 0, 0); int* tmp = new int[(dfc + 1)]; for (int i = 0; i <= dfc; i++) { tmp[i] = i; } st.init(dfc, tmp, [&] (int x, int y) { return edep[x] < edep[y]; }); for (int i = 1, u, v; i <= m; i++) { scanf("%d%d", &u, &v); Vi[u].push_back(ein[u]); Vi[u].push_back(ein[v]); Vi[v].push_back(ein[u]); Vi[v].push_back(ein[v]); int g = lca(u, v); Vo[g].push_back(ein[u]); Vo[g].push_back(ein[v]); } solve(1, 0); ans >>= 2; printf("%lld\n", ans); return 0; }
Problem C 浙江省选
考虑最终能成为 rk 1 的是直接半平交。考虑求最终能成为 rk 2 的,把剩下的求半平面交,把剩下可能成为 rk 1 的判断一下它出现在凸包上的区间是否被本来是 rk 1 的覆盖。具体地来说你可以用之前是 rk 1 的直线,二分一下它覆盖了凸包上哪些区间(显然这样的区间只有常数个)。这样就能找到所有能成为 rk 2 的直线。对于 rk 更大的情况只用变更为计算被覆盖的次数就行了。
注意坐标必须是整数,以及 long long 的运算可能会炸 long long。
Code
#include <bits/stdc++.h> using namespace std; const int N = 1e5 + 5; typedef long long ll; typedef __int128 LL; const ll llf = (signed long long) (~0ull >> 2); typedef class Fraction { public: ll a, b; Fraction() { } Fraction(ll x, ll y) : a(x), b(y) { if (b < 0) { b = -b; a = -a; } } bool operator < (Fraction x) const { return ((LL) a) * x.b < ((LL) b) * x.a; } bool operator <= (Fraction x) const { return ((LL) a) * x.b <= ((LL) b) * x.a; } bool operator == (Fraction x) const { return ((LL) a) * x.b == ((LL) b) * x.a; } ll floor() { return a < 0 ? ((a - b + 1) / b) : (a / b); } ll ceil() { return a < 0 ? (a / b) : ((a + b - 1) / b); } } Fraction; typedef class Line { public: ll a, b; int id; Fraction intersect(Line x) { assert(a != x.a); return Fraction(x.b - b, a - x.a); } void read() { scanf("%lld%lld", &a, &b); } } Line; typedef class Event { public: int op, v; // 0 : insert, 1 : cover Fraction p; Event(int op, int v, Fraction p) : op(op), v(v), p(p) { } bool operator < (Event b) const { return p == b.p ? (op == b.op ? v > b.v : op < b.op) : p < b.p; } } Event; int n, m; Line li[N]; Fraction fl[N], fr[N]; bool ban[N]; int ans[N]; Line stk[N]; vector<Event> E; vector<int> ncand; vector<int> candidate; const Fraction f0 (0, 1); int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) { li[i].read(); li[i].id = i; ans[i] = -1; } sort(li + 1, li + n + 1, [&] (Line a, Line b) { return a.a == b.a ? a.b > b.b : a.a < b.a; }); for (int r = 1; r <= m; r++) { int tp = 0; ll lsa = 0; fill(ban + 1, ban + n + 1, false); for (int i = 1; i <= n; i++) { if (ans[li[i].id] == -1 && li[i].a != lsa) { lsa = li[i].a; while (tp >= 2 && stk[tp].intersect(li[i]) < stk[tp - 1].intersect(stk[tp])) tp--; stk[++tp] = li[i]; } else if (li[i].a == lsa) { ban[i] = true; } } if (!tp) { break; } E.clear(); int cov = 0; for (int i = 1; i <= n; i++) { if (ban[i]) { continue; } int l = 2, r = tp, mid; Line& lc = li[i]; int id = li[i].id; while (l <= r) { mid = (l + r) >> 1; if (stk[mid].a < lc.a && stk[mid].intersect(stk[mid - 1]) < lc.intersect(stk[mid])) { l = mid + 1; } else { r = mid - 1; } } --l; if (stk[l].a >= lc.a) { fl[i] = Fraction(-llf, 1); } else { fl[i] = stk[l].intersect(lc); } l = 1, r = tp - 1; while (l <= r) { mid = (l + r) >> 1; if (stk[mid].a > lc.a && lc.intersect(stk[mid]) < stk[mid].intersect(stk[mid + 1])) { r = mid - 1; } else { l = mid + 1; } } ++r; if (stk[r].a <= lc.a) { fr[i] = Fraction(llf, 1); } else { fr[i] = lc.intersect(stk[r]); } if (fr[i] < fl[i] || fr[i] < f0) { continue; } if (ans[id] != -1) { if (fl[i] < f0) { cov++; } else { E.emplace_back(1, 1, fl[i]); } if (fr[i].a != llf) { E.emplace_back(1, -1, fr[i]); } } else { E.emplace_back(0, i, fl[i]); } } sort(E.begin(), E.end()); Fraction lsf = f0; bool have_first = cov < r; for (auto it = E.begin(), nit = it, _it = E.end(); it != _it; it = nit) { while (nit != _it && (*nit).p == (*it).p) { auto e = *nit; if (!e.op) { candidate.push_back(e.v); } else { cov += e.v; } nit++; } auto p = (*it).p; if (cov < r) { if (!have_first) { have_first = true; lsf = p; } } else if (have_first) { if (p.floor() >= lsf.ceil()) { ncand.clear(); for (auto x : candidate) { auto L = max(fl[x], lsf); auto R = min(fr[x], p); if (L.ceil() <= R.floor()) { ans[li[x].id] = r; } else if (lsf < fl[x] && p <= fr[x]) { ncand.push_back(x); } } candidate = ncand; } have_first = false; } } if (cov < r) { for (auto x : candidate) { auto L = max(fl[x], lsf); auto R = fr[x]; if (L.ceil() <= R.floor()) { ans[li[x].id] = r; } } } candidate.clear(); } for (int i = 1; i <= n; i++) { printf("%d%c", ans[i], " \n"[i == n]); } return 0; }