NOI 2019 简要题解
从这里开始
说好的 agc 046 呢
去年的题真难写
Day 1
Problem A 回家路线
暴力即可。 2e8 真的很稳。
可以按开始时间排序,然后每个点上斜率优化。
Code
#include <bits/stdc++.h> using namespace std; const int N = 2e5 + 5; #define ll long long const ll llf = (signed ll) (~0ull >> 3); typedef class Point { public: int x; ll y; Point(int x = 0, ll y = 0) : x(x), y(y) { } } Point; Point operator - (Point a, Point b) { return Point(a.x - b.x, a.y - b.y); } ll cross(Point a, Point b) { return a.x * b.y - a.y * b.x; } typedef class ConvexHull { public: int pos; vector<Point> stk; ConvexHull() : pos(0) { } void insert(int _x, ll _y) { Point p (_x, _y); while (!stk.empty() && stk.back().x == _x && stk.back().y >= _y) stk.pop_back(); while (stk.size() > 1u && cross(stk.back() - stk[stk.size() - 2], p - stk.back()) <= 0) stk.pop_back(); stk.push_back(p); } ll query(ll k) { if (stk.empty()) { return llf; } ll ret = llf; for (int i = 0; i < (signed) stk.size(); i++) { ret = min(ret, stk[i].y - stk[i].x * k); } pos = min(pos, (signed) stk.size() - 1); while (pos < (signed) stk.size() - 1 && stk[pos].y - k * stk[pos].x > stk[pos + 1].y - k * stk[pos + 1].x) pos++; return stk[pos].y - k * stk[pos].x; } } ConvexHull; typedef class Route { public: int s, t, p, q; void read() { scanf("%d%d%d%d", &s, &t, &p, &q); } bool operator < (Route b) const { return p < b.p; } } Route; int n, m, A, B, C; ll f[N]; Route R[N]; int ord[N]; ConvexHull con[N]; int main() { freopen("route.in", "r", stdin); freopen("route.out", "w", stdout); scanf("%d%d%d%d%d", &n, &m, &A, &B, &C); for (int i = 1; i <= m; i++) { R[i].read(); assert(R[i].p < R[i].q); } sort(R + 1, R + m + 1); R[0].t = 1, R[0].q = 0; for (int i = 0; i <= m; i++) { ord[i] = i; } sort(ord + 1, ord + m + 1, [&] (int x, int y) { return R[x].q < R[y].q; }); ll ans = llf; int* po = ord, *_po = ord + m + 1; for (int i = 1; i <= m; i++) { while (po != _po && R[*po].q <= R[i].p) { if (f[*po] < (llf >> 1)) { Route &r = R[*po]; con[r.t].insert(r.q, f[*po] + 1ll * A * r.q * r.q - 1ll * B * r.q); } po++; } Route& r = R[i]; f[i] = con[r.s].query(2 * A * r.p); if (f[i] >= (llf >> 1)) continue; f[i] += 1ll * A * r.p * r.p + 1ll * B * r.p + C; if (r.t == n) { ans = min(ans, f[i] + r.q); } } printf("%lld\n", ans); return 0; }
Problem B 机器人
显然答案是一个关于最大能选的数的不超过 $n$ 次的分段多项式。
写一个暴力打表发现涉及到的区间的长度和最长也就 17400 左右,因为一个点可能因为 +1 被加上 $O(log n)$ 次,大概总的段数也在 $10^5$ 左右。直接用点值维护分段多项式就行了。据说直接暴力维护出系数表示的多项式也能过。
Code
#include <bits/stdc++.h> using namespace std; const int N = 305; const int Mod = 1e9 + 7; const int inf = (~0u >> 2); #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 x, y; exgcd(a, Mod, x, y); return x < 0 ? x + Mod : x; } typedef class Zi { public: int v; Zi() { } Zi(int v) : v(v) { } Zi(ll x) : v(x % Mod) { } friend Zi operator + (Zi a, Zi b) { int x = a.v + b.v; return x >= Mod ? x - Mod : x; } friend Zi operator - (Zi a, Zi b) { int x = a.v -b.v; return x < 0 ? x + Mod : x; } friend Zi operator * (Zi a, Zi b) { return 1ll * a.v * b.v; } friend Zi operator ~ (Zi a) { return inv(a.v); } Zi& operator += (Zi b) { return *this = *this + b; } Zi& operator -= (Zi b) { return *this = *this - b; } Zi& operator *= (Zi b) { return *this = *this * b; } } Zi; vector<Zi> Inv; vector<Zi> fac, _fac; void prepare(int n) { Inv.resize(n + 1); for (int i = 1; i <= n; i++) { Inv[i] = ~Zi(i); } fac.resize(n + 1); _fac.resize(n + 1); fac[0] = 1; for (int i = 1; i <= n; i++) { fac[i] = fac[i - 1] * i; } _fac[n] = ~fac[n]; for (int i = n; i; i--) { _fac[i - 1] = _fac[i] * i; } } int n, S; typedef class Segment { public: int r; vector<Zi> f; Segment() { } Segment(int r, vector<Zi> f) : r(r), f(f) { } void init(Zi x) { f.resize(S, x); } Zi eval(Zi n) { static Zi L[N], R[N]; if (n.v < S) { return f[n.v]; } L[0] = 1; for (int i = 0; i < S; i++) { L[i + 1] = L[i] * (n - i); } R[S] = 1; for (int i = S; i--; ) { R[i] = R[i + 1] * (n - i); } Zi ret = 0; for (int i = 0; i < S; i++) { Zi tmp = f[i] * L[i] * R[i + 1] * _fac[i] * _fac[S - 1 - i]; if ((S - 1 - i) & 1) { ret -= tmp; } else { ret += tmp; } } return ret; } Segment operator + (const Segment& b) { Segment s; s.init(S); s.r = min(r, b.r); for (int i = 0; i < S; i++) { s.f[i] = f[i] + b.f[i]; } return s; } Segment operator * (const Segment& b) { Segment s; s.init(S); s.r = min(r, b.r); for (int i = 0; i < S; i++) { s.f[i] = f[i] * b.f[i]; } return s; } Segment& operator += (Zi x) { for (auto& y : f) y += x; return *this; } void pre_sum() { Zi sum = 0; for (int i = 0; i < S; i++) { sum += f[i]; f[i] = sum; } } void shift() { r = min(r + 1, inf); f.insert(f.begin(), eval(Mod - 1)); f.pop_back(); } } Segment; typedef class Function { public: vector<Segment> seg; void append(Segment s) { seg.push_back(s); } void pre_sum() { int ls = 0; Zi sum = 0; for (auto&s : seg) { s.pre_sum(); s += (sum - s.eval(ls)); ls = s.r; sum = s.eval(s.r); } } Function operator * (const Function& b) { Function f; auto pl = seg.begin(), _pl = seg.end(); auto pr = b.seg.begin(), _pr = b.seg.end(); while (pl != _pl && pr != _pr) { if ((*pl).r == (*pr).r) { f.append(*(pl++) * *(pr++)); } else if ((*pl).r < (*pr).r) { f.append(*(pl++) * *pr); } else { f.append(*pl * *(pr++)); } } assert(pl == _pl && pr == _pr); return f; } Function operator + (const Function& b) { Function f; auto pl = seg.begin(), _pl = seg.end(); auto pr = b.seg.begin(), _pr = b.seg.end(); while (pl != _pl && pr != _pr) { if ((*pl).r == (*pr).r) { f.append(*(pl++) + *(pr++)); } else if ((*pl).r < (*pr).r) { f.append(*(pl++) + *pr); } else { f.append(*pl + *(pr++)); } } assert(pl == _pl && pr == _pr); return f; } Function& reserve(int l, int r) { for (int i = seg.size(); i--; ) { if (!i || seg[i - 1].r < r) { seg[i].r = r; seg.erase(seg.begin() + i + 1, seg.end()); break; } } seg.push_back(Segment(inf, vector<Zi>(S, 0))); if (l == 1) return *this; for (int i = 0; i < (seg.size()); i++) { if (seg[i].r >= l) { seg.erase(seg.begin(), seg.begin() + i); break; } } seg.insert(seg.begin(), Segment(l - 1, vector<Zi>(S, 0))); return *this; } Function& shift() { for (auto& s : seg) { s.shift(); } seg.insert(seg.begin(), Segment(1, vector<Zi>(S, 0))); return *this; } void shrink() { vector<Segment> nseg; int ls = 0; for (auto& s : seg) { if (s.r > ls) { nseg.push_back(s); ls = s.r; } } seg = nseg; } } Function; int A[N], B[N]; bool vis[N][N]; Function f0, fe, f[N][N]; int Abs(int x) { return x < 0 ? -x : x; } Function solve(int l, int r) { if (l > r) { return fe; } if (vis[l][r]) { return f[l][r]; } vis[l][r] = true; Function& F = f[l][r]; F = f0; for (int i = l; i <= r; i++) { if (Abs((i - l) - (r - i)) <= 2) { F = F + (solve(l, i - 1) * ((i + 1 <= r) ? solve(i + 1, r).shift() : fe)).reserve(A[i], B[i]); } } F.shrink(); F.pre_sum(); return F; } int main() { freopen("robot.in", "r", stdin); freopen("robot.out", "w", stdout); scanf("%d", &n); S = n + 2; prepare(S + 3); for (int i = 1; i <= n; i++) { scanf("%d%d", A + i, B + i); } f0.append(Segment(inf, vector<Zi>(S, 0))); fe.append(Segment(inf, vector<Zi>(S, 1))); Function fans = solve(1, n); Zi ans = fans.seg.back().eval(inf); printf("%d\n", ans.v); return 0; }
Problem C 序列
开大数组可以得到比预估更高的分数,学到了。
有一个比较好想的建图,然而大概平方版本跑不过 2e3,模拟费用流跑不过 3e5。 d1 t3 还卡常真有毒。
考虑要求每次选择选择一个 $x$ 也选择一个 $y$,这样能够很好地限制它们数量相等。
建两列点,$A_i$ 向 $B_i$ 连边,再建一个一对点 $x, x'$,连边 $(x, x', K - L , 0)$,用于限制选择不同的一对的数量。然后 $A_i$ 向 $x$ 连边,$x'$ 向 $B_i$ 连边。$S$ 向 $A_i$ 连边,$B_i$ 向 $T$ 连边。
考虑怎么费用流:
- 如果 $x \rightarrow$ 未满流了,那么无论如何都相当于左边选择最大,右边再选择一个最大的,然后再判断它的流量会不会增加。
- 否则有 $4$ 种情况
- $S \rightarrow A_i \rightarrow B_i \rightarrow T$
- $S \rightarrow A_i \rightarrow x \rightarrow A_j \rightarrow B_j \rightarrow T$
- $S \rightarrow A_i \rightarrow B_i \rightarrow x' \rightarrow B_j \rightarrow T$
- $S \rightarrow A_i \rightarrow B_i \rightarrow x' \rightarrow x \rightarrow A_j \rightarrow B_j \rightarrow T$
然后瞎维护一下就行了
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; typedef class Input { protected: const static int limit = 65536; FILE* file; int ss, st; char buf[limit]; public: Input() : file(NULL) { }; Input(FILE* file) : file(file) { } void open(FILE *file) { this->file = file; } void open(const char* filename) { file = fopen(filename, "r"); } char pick() { if (ss == st) st = fread(buf, 1, limit, file), ss = 0;//, cerr << "str: " << buf << "ed " << st << endl; return buf[ss++]; } } Input; #define digit(_x) ((_x) >= '0' && (_x) <= '9') Input& operator >> (Input& in, unsigned& u) { char x; while (~(x = in.pick()) && !digit(x)); for (u = x - '0'; ~(x = in.pick()) && digit(x); u = u * 10 + x - '0'); return in; } Input& operator >> (Input& in, unsigned long long& u) { char x; while (~(x = in.pick()) && !digit(x)); for (u = x - '0'; ~(x = in.pick()) && digit(x); u = u * 10 + x - '0'); return in; } Input& operator >> (Input& in, int& u) { char x; while (~(x = in.pick()) && !digit(x) && x != '-'); int aflag = ((x == '-') ? (x = in.pick(), -1) : (1)); for (u = x - '0'; ~(x = in.pick()) && digit(x); u = u * 10 + x - '0'); u *= aflag; return in; } Input& operator >> (Input& in, long long& u) { char x; while (~(x = in.pick()) && !digit(x) && x != '-'); int aflag = ((x == '-') ? (x = in.pick(), -1) : (1)); for (u = x - '0'; ~(x = in.pick()) && digit(x); u = u * 10 + x - '0'); u *= aflag; return in; } Input& operator >> (Input& in, double& u) { char x; while (~(x = in.pick()) && !digit(x) && x != '-'); int aflag = ((x == '-') ? (x = in.pick(), -1) : (1)); for (u = x - '0'; ~(x = in.pick()) && digit(x); u = u * 10 + x - '0'); if (x == '.') { double dec = 1; for ( ; ~(x = in.pick()) && digit(x); u = u + (dec *= 0.1) * (x - '0')); } u *= aflag; return in; } Input& operator >> (Input& in, char* str) { char x; while (~(x = in.pick()) && x != '\n' && x != ' ') *(str++) = x; *str = 0; return in; } Input in (fopen("sequence.in", "r")); typedef class Output { protected: const static int Limit = 65536; char *tp, *ed; char buf[Limit]; FILE* file; int precision; void flush() { fwrite(buf, 1, tp - buf, file); fflush(file); tp = buf; } public: Output() { } Output(FILE* file) : tp(buf), ed(buf + Limit), file(file), precision(6) { } Output(const char *str) : tp(buf), ed(buf + Limit), precision(6) { file = fopen(str, "w"); } ~Output() { flush(); } void put(char x) { if (tp == ed) flush(); *(tp++) = x; } int get_precision() { return precision; } void set_percision(int x) { precision = x; } } Output; Output& operator << (Output& out, int x) { static char buf[35]; static char * const lim = buf + 34; if (!x) out.put('0'); else { if (x < 0) out.put('-'), x = -x; char *tp = lim; for ( ; x; *(--tp) = x % 10, x /= 10); for ( ; tp != lim; out.put(*(tp++) + '0')); } return out; } Output& operator << (Output& out, long long x) { static char buf[36]; static char * const lim = buf + 34; if (!x) out.put('0'); else { if (x < 0) out.put('-'), x = -x; char *tp = lim; for ( ; x; *(--tp) = x % 10, x /= 10); for ( ; tp != lim; out.put(*(tp++) + '0')); } return out; } Output& operator << (Output& out, unsigned x) { static char buf[35]; static char * const lim = buf + 34; if (!x) out.put('0'); else { char *tp = lim; for ( ; x; *(--tp) = x % 10, x /= 10); for ( ; tp != lim; out.put(*(tp++) + '0')); } return out; } Output& operator << (Output& out, char x) { out.put(x); return out; } Output& operator << (Output& out, const char* str) { for ( ; *str; out.put(*(str++))); return out; } Output& operator << (Output& out, double x) { int y = x; x -= y; out << y << '.'; for (int i = out.get_precision(); i; i--, y = x * 10, x = x * 10 - y, out.put(y + '0')); return out; } Output out ("sequence.out"); #define pii pair<int, int> #define ll long long const int inf = (signed) (~0u >> 2); typedef class ZKW { public: int M; pii mx[524288]; void init(int n, pii* w) { for (M = 1; M < n; M <<= 1); for (int i = 0; i < n; i++) { mx[i + M] = w[i]; } for (int i = n; i < M; i++) { mx[i + M] = pii(-inf, 0); } for (int i = M; --i; push_up(i)); } void push_up(int p) { mx[p] = max(mx[p << 1], mx[p << 1 | 1]); } void upd(int p, int vi) { p += M; mx[p] = pii(vi, p - M); for ( ; p >>= 1; push_up(p)); } int qry() { return mx[1].first; } int qryi() { return mx[1].second; } } ZKW; const int N = 2e5 + 5; int T, n, K, L; int cnt; int a[N], b[N]; ZKW zs, za, zb, _za, _zb; bool ina[N], inb[N]; void seclecta(int p) { ina[p] = true; cnt -= ina[p] && inb[p]; zs.upd(p, -inf); za.upd(p, -inf); _za.upd(p, -inf); if (!inb[p]) _zb.upd(p, b[p]); } void seclectb(int p) { inb[p] = true; cnt -= ina[p] && inb[p]; zs.upd(p, -inf); zb.upd(p, -inf); _zb.upd(p, -inf); if (!ina[p]) _za.upd(p, a[p]); } void solve() { static pii tmp[N]; cnt = 0; in >> n >> K >> L; for (int i = 0; i < n; i++) { in >> a[i]; } for (int i = 0; i < n; i++) { in >> b[i]; } for (int i = 0; i < n; i++) tmp[i] = pii(a[i] + b[i], i); zs.init(n, tmp); for (int i = 0; i < n; i++) tmp[i] = pii(a[i], i); za.init(n, tmp); for (int i = 0; i < n; i++) tmp[i] = pii(b[i], i); zb.init(n, tmp); for (int i = 0; i < n; i++) tmp[i] = pii(-inf, i); _za.init(n, tmp); _zb.init(n, tmp); fill(ina, ina + n, false); fill(inb, inb + n, false); for (int i = 1; i <= K; i++) { if (cnt == K - L) { int swpa = _za.qry() + zb.qry(); int swpb = za.qry() + _zb.qry(); int mx = zs.qry(), id = 0, q; if ((q = _za.qry() + _zb.qry()) > mx) id = 1, mx = q; if ((q = swpa) > mx) id = 2, mx = q; if ((q = swpb) > mx) id = 3, mx = q; if (!id) { q = zs.qryi(); seclecta(q); seclectb(q); } else if (id == 1) { seclecta(_za.qryi()); seclectb(_zb.qryi()); } else if (id == 2) { seclecta(_za.qryi()); seclectb(zb.qryi()); } else { seclecta(za.qryi()); seclectb(_zb.qryi()); } } else { seclecta(za.qryi()); seclectb(zb.qryi()); } ++cnt; } ll ans = 0; for (int i = 0; i < n; i++) { ans += ina[i] * a[i]; ans += inb[i] * b[i]; } out << ans << '\n'; } int main() { in >> T; while (T--) { solve(); } return 0; }
Day 2
Problem A 弹跳
考虑 dijkstra 的过程,每次我们加入一条边。一条边更新一个点后,一个点就不可能再被更新了。因此我们只用查询一个矩形内的所有点然后把它们删除。
可以用线段树套 set 或者 KDtree 维护。
时间复杂度 $O(n\log^2 n + m\log n)$。
假设对 $x$ 建线段树,考虑将所有查询矩形和点先按照 $y$ 从小到大排序,依次加入(矩形只用在定位到的节点上加入它的下边界),每个节点上做一次归并,这样可以预处理出每次矩形删点的 lower_bound 的结果。删点的过程显然可以用并查集优化。
理论上可以做到 $O(n\log n + m \log n)$
Code
/** * loj * Problem#3159 * Accepted * Time: 6467ms * Memory: 51568k */ #include <bits/stdc++.h> using namespace std; typedef bool boolean; const int N = 7e4 + 5; const int M = 150005; typedef class Node { public: int eid, dis; Node() { } Node(int eid, int dis) : eid(eid), dis(dis) { } boolean operator < (Node b) const { return dis > b.dis; } } Node; #define pii pair<int, int> int n, m, w, h; int f[N]; boolean vis[N]; vector<int> G[N]; set<pii> S[N << 2]; priority_queue<Node> Q; int xl[M], xr[M], yl[M], yr[M], W[M]; void insert(int p, int l, int r, int x, int y, int id) { S[p].insert(pii(y, id)); if (l == r) return; int mid = (l + r) >> 1; if (x <= mid) { insert(p << 1, l, mid, x, y, id); } else { insert(p << 1 | 1, mid + 1, r, x, y, id); } } void del(int p, int l, int r, int x1, int x2, int y1, int y2, int w) { if (l == x1 && r == x2) { set<pii>::iterator it = S[p].lower_bound(pii(y1, 0)); while (it != S[p].end() && (*it).first <= y2) { int id = (*it).second; if (!vis[id]) { vis[id] = true; f[id] = w; for (auto eid : G[id]) { Q.push(Node(eid, f[id] + W[eid])); } } it = S[p].erase(it); } return; } int mid = (l + r) >> 1; if (x2 <= mid) { del(p << 1, l, mid, x1, x2, y1, y2, w); } else if (x1 > mid) { del(p << 1 | 1, mid + 1, r, x1, x2, y1, y2, w); } else { del(p << 1, l, mid, x1, mid, y1, y2, w); del(p << 1 | 1, mid + 1, r, mid + 1, x2, y1, y2, w); } } int main() { freopen("jump.in", "r", stdin); freopen("jump.out", "w", stdout); scanf("%d%d%d%d", &n, &m, &w, &h); for (int i = 1, x, y; i <= n; i++) { scanf("%d%d", &x, &y); if (i ^ 1) { insert(1, 1, w, x, y, i); } } for (int i = 1, p; i <= m; i++) { scanf("%d%d%d%d%d%d", &p, W + i, xl + i, xr + i, yl + i, yr + i); G[p].push_back(i); } for (auto e : G[1]) { Q.push(Node(e, W[e])); } while (!Q.empty()) { int e = Q.top().eid; int d = Q.top().dis; Q.pop(); del(1, 1, w, xl[e], xr[e], yl[e], yr[e], d); } for (int i = 2; i <= n; i++) { printf("%d\n", f[i]); } return 0; }
Problem B 斗主地
开始听说看过具体数学就会,后来听说打个表也没了,当时我就不一样,既没读过书,也不会打表
考虑直接计算 $E_i$ 贡献到 $E'_j$ 的系数。不妨先考虑 $i \leqslant A$ 的情形
$$
\begin{align}
\binom{n - j}{A - i} \frac{A^{\underline{A-i+1}}(n-A)^{\underline{n - j - (A- i)}}}{n^{\underline{n - j + 1}}} &= \binom{n}{A}^{-1}\binom{n-j}{A- i}\binom{j-1}{i-1}
\end{align}
$$
然后 $E'_j$ 会对 $i \leqslant A$ 的 $E_i$ 乘上这个系数进行求和。
先考虑 $E_i = i$ 的时候,提出 $\binom{n}{A}^{-1}$, 考虑这个式子的组合意义:从 $n$ 个球选出 $A$ 个球,第 $j$ 个球一定被选择,如果是第 $i$ 个被选择的球,贡献为 $i$。
考虑枚举被选择的球,不难得到它等于 $\binom{n - 1}{A - 1} + (j - 1)\binom{n - 2}{A - 2}$。
对于 $i > A$ 的时候是类似的做法。
容易发现 $E'_j$ 是关于 $j$ 的一次多项式。
当 $type = 2$ 的时候是关于 $j$ 的二次多项式。
可以暴力维护,也可以插值。
插值竞赛(确信
Code
#include <bits/stdc++.h> using namespace std; #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; } }; 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; int n, m, type, q; vector<Zi> fac, _fac; void prepare(int n) { fac.resize(n + 1); _fac.resize(n + 1); fac[0] = 1; for (int i = 1; i <= n; i++) { fac[i] = fac[i - 1] * i; } _fac[n] = ~fac[n]; for (int i = n; i; i--) { _fac[i - 1] = _fac[i] * i; } } Zi comb(int n, int m) { assert(n >= 0 && m >= 0); return n < m ? 0 : fac[n] * _fac[m] * _fac[n - m]; } Zi _comb(int n, int m) { return _fac[n] * fac[m] * fac[n - m]; } typedef vector<Zi> vec; vec operator * (vec a, Zi k) { for (auto& x : a) x *= k; return a; } vec operator + (vec a, vec b) { if (a.size() < b.size()) a.resize(b.size()); for (int i = 0; i < (signed) b.size(); i++) a[i] += b[i]; return a; } Zi eval(int A, int c) { return A - 1 < c ? 0 : comb(n - 1 - c, A - 1 - c); } vec get1(int A) { vec v (2); v[0] = eval(n - A, 0) * A; v[1] = eval(A, 1) + eval(n - A, 1); return v * _comb(n, A); } vec get2(int A) { vec v (3); v[0] = eval(n - A, 0) * A * (A - 1) * ((Mod + 1) >> 1); v[1] = eval(n - A, 1) * A; v[2] = eval(A, 2) + eval(n - A, 2); return v * _comb(n, A); } int main() { freopen("landlords.in", "r", stdin); freopen("landlords.out", "w", stdout); scanf("%d%d%d", &n, &m, &type); prepare(n + 1); vec f, g; if (type == 1) { f = {1, 1}; } else { f = {1, 3, 2}; } for (int i = 1, a; i <= m; i++) { scanf("%d", &a); g = vec {f[0]}; g = g + get1(a) * f[1]; if (type == 2) g = g + get2(a) * f[2]; f = g; } scanf("%d", &q); Zi x; while (q--) { scanf("%d", &x.v); Zi ans = f[0] + f[1] * (x - 1); if (type == 2) ans += f[2] * (x - 1) * (x - 2) * ((Mod + 1) >> 1); printf("%d\n", ans.v); } return 0; }
Problem C I 君的探险
A:不难得到每个点周围一圈的异或和,做完了。
B:直接整体二分。
对于树的部分分考虑可以假设一个点是叶子,然后用 2 次修改 1 次询问检查它是不是,因此我们可以判断一个点是不是叶子。然后每次剥叶子就可以了。
对于一般图的情形,考虑随机一个排列,然后套用 B 的整体二分做法,如果一个点向前连了奇数条边,那么一定能找到一条边,否则可能能找到。
不难证明最坏情况下,期望能减少 $\frac{t}{3}$ 条边,其中 $t$ 是度数非 0 的点的数量。因此当有孤立点的时候复杂度有点假,需要删除孤立点。
否则的话,可以花费 $O(n\log n)$ 的代价,期望减少 $\frac{n}{3}$ 的边。因此期望的操作次数和复杂度均是 $O(m\log n)$。
至于初始孤立点我好像只会一些常数比较劣的 $O(n\log n)$ 随机化做法,加了估计操作次数会超,不过数据好像没有卡不处理初始孤立点的情况。
Code
#include <bits/stdc++.h> #include "explore.h" using namespace std; int N, M; namespace brute { vector<int> old; void solve() { old.resize(N, 0); for (int i = 0; i < N - 1; i++) { modify(i); for (int j = i + 1; j < N; j++) { int x = query(j); if (x ^ old[j]) { report(i, j); old[j] = x; } } } } } namespace subtaskA { vector<int> v; vector<int> old; void solve() { v.assign(N, 0); old.assign(N, 0); for (int b = 1; b < N; b <<= 1) { for (int i = 0; i < N; i++) { if (i & b) { modify(i); } } for (int i = 0; i < N; i++) { int x = query(i); if (old[i] ^ x) { v[i] |= b; old[i] = x; } } } for (int i = 0; i < N; i++) { if ((v[i] ^ i) < i) { report(v[i] ^ i, i); } } } } namespace subtaskB { void dividing(int l, int r, vector<int> p) { if (l == r) { for (auto x : p) { report(l, x); } return; } if (p.empty()) { return; } int mid = (l + r) >> 1; for (int i = l; i <= mid; i++) { modify(i); } vector<int> vl, vr; for (auto x : p) { if (x <= mid || query(x)) { vl.push_back(x); } else { vr.push_back(x); } } for (int i = l; i <= mid; i++) { modify(i); } dividing(l, mid, vl); dividing(mid + 1, r, vr); } void solve() { vector<int> p (N - 1); for (int i = 1; i < N; i++) p[i - 1] = i; dividing(0, N - 1, p); } } namespace general { vector<int> P; vector<int> on; vector<int> tg; vector<bool> exist; vector<vector<int>> G; void answer(int x, int y) { report(x, y); G[x].push_back(y); G[y].push_back(x); M--; if (check(x)) { exist[x] = false; } if (check(y)) { exist[y] = false; } } void upd(int p) { modify(p); on[p] ^= 1; } bool qry(int p) { bool ret = query(p); for (auto x : G[p]) { ret ^= on[x]; } return ret ^ tg[p]; } void light_on(int l, int r) { static int ql = 0, qr = -1; if (l < 0) { for (int i = ql; i <= qr; i++) { upd(P[i]); } ql = 0, qr = -1; return; } for (int i = ql; i <= qr; i++) { if (i < l || i > r) { upd(P[i]); } } for (int i = l; i <= r; i++) { if (!on[P[i]]) { upd(P[i]); } } ql = l, qr = r; } void dividing(int l, int r, vector<int> S) { if (S.empty()) { return; } if (l == r) { int p = P[l]; light_on(l, l); for (auto x : S) { if (P[x] != p && qry(P[x])) { answer(p, P[x]); } } return; } int mid = (l + r) >> 1; light_on(l, mid); vector<int> sl, sr; for (auto x : S) { if (x <= mid || qry(P[x])) { sl.push_back(x); } else { sr.push_back(x); } } dividing(l, mid, sl); dividing(mid + 1, r, sr); } void solve() { P.resize(N); tg.assign(N, 0); on.assign(N, false); exist.assign(N, true); G.assign(N, vector<int>()); for (int i = 0; i < N; i++) P[i] = i; vector<int> P0 = P; while (M) { light_on(-1, 0); vector<int> tmp; for (auto x : P) { if (exist[x]) { tmp.push_back(x); } } P = tmp; P0.resize(P.size()); random_shuffle(P.begin(), P.end()); // cerr << P.size() << " " << " " << M << '\n'; dividing(0, (signed) P.size() - 1, P0); } } } void explore(int N, int M) { srand(time(NULL)); // srand(233u); ::N = N; ::M = M; if (N <= 500) { brute::solve(); } else if (N % 10 == 8) { subtaskA::solve(); } else if (N % 10 == 7) { subtaskB::solve(); } else { general::solve(); } }