UOJ Round #11 简要题解
从这里开始
说好的 agc 045 题解去哪了
Problem A 元旦老人与汉诺塔
直接状压每个盘子在哪个柱子,记忆化搜索即可。
时间复杂度 O(能过)。
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; #define ull __int128 const int Mod = 998244353; int n, m; typedef class Status { public: ull a, b, c; int step; Status() { } Status(ull a, ull b, ull c, int step) : a(a), b(b), c(c), step(step) { } void read() { for (int i = 0, x; i < n; i++) { scanf("%d", &x); if (x == 1) { a |= 1ull << i; } else if (x == 2) { b |= 1ull << i; } else { c |= 1ull << i; } } } bool operator < (Status t) const { if (a ^ t.a) return a < t.a; if (b ^ t.b) return b < t.b; if (c ^ t.c) return c < t.c; return step < t.step; } bool equal(Status t) { return a == t.a && b == t.b && c == t.c; } } Status; Status ss, st; map<Status, int> f; void inc(int& x) { if (x >= Mod) { x -= Mod; } } int F(Status s) { if (s.step > m) { return 0; } if (f.count(s)) { return f[s]; } ull a = s.a & (-s.a); ull b = s.b & (-s.b); ull c = s.c & (-s.c); int ret = s.equal(st); if (a) { if (a < b || !b) inc(ret += F(Status(s.a ^ a, s.b ^ a, s.c, s.step + 1))); if (a < c || !c) inc(ret += F(Status(s.a ^ a, s.b, s.c ^ a, s.step + 1))); } if (b) { if (b < a || !a) inc(ret += F(Status(s.a ^ b, s.b ^ b, s.c, s.step + 1))); if (b < c || !c) inc(ret += F(Status(s.a, s.b ^ b, s.c ^ b, s.step + 1))); } if (c) { if (c < a || !a) inc(ret += F(Status(s.a ^ c, s.b, s.c ^ c, s.step + 1))); if (c < b || !b) inc(ret += F(Status(s.a, s.b ^ c, s.c ^ c, s.step + 1))); } return f[s] = ret; } int main() { scanf("%d%d", &n, &m); ss.read(); st.read(); int ans = F(ss); printf("%d\n", ans); return 0; }
Problem B 元旦老人与丛林
注意到一个必要条件是任意一个点集 $V$ 的导出子图的边集大小不会超过 $2|V| - 2$。可以证明这个是充分的。
证明考虑归纳法,当 $n = 1$ 的时候显然满足。
现在考虑 $n > 1$ 的情况,并假设 $n$ 更小的时候成立。
考虑图中度数最小的 $x$,那么 $x$ 的度数只可能为 $0, 1, 2, 3$。
对于前 3 种情况可以简单地分配与它相邻的边使得它在 2 个新图中的度数均不超过 1。下面找考虑 $x$ 的度数为 3 的情况。
我们称一个子图 $G = (V, E)$ 是满的,当且仅当 $2|V| - 2 = |E|$。
引理 如果 $G_1 = (V_1, E_1), G_2 = (V_2, E_2)$ 都是满子图,并且 $V_1 \cap V_2 \neq nothing$,那么 $G = (V_1 \cup V_2, E_1 \cup E_2), G_3 = (V_1 \cap V_2, E_1 \cap E_2)$ 都是满子图。
证明 $|E_1 \cup E_2| = |E_1| + |E_2| - |E_1\cap E_2| \geqslant 2|V_1| - 2 + 2|V_2| - 2 - (2|V_1\cap V_2| - 2) = 2|V_1 \cup V_2| - 2$,又因为 $|E_1 \cup E_2| \leqslant 2|V_1 \cup V_2| - 2$,所以有 $|E_1 \cup E_2| =2|V_1 \cup V_2| - 2$。
用类似的方法可以证得 $G_3$ 是满子图。
考虑和 $x$ 相邻的三个点 $a, b, c$。设删掉 $x$ 及其相邻的边后得到的图是 $G'$。
定理 $(a, b), (b, c), (a, c)$ 中至少存在一对 $(u, v)$ 满足同时包含 $(u, v)$ 的导出子图都不是满子图
证明 考虑反证法,在这三对各找一个使得它是满子图的子图,然后把它们并起来,这样得到了一个同时包含 $a, b, c$ 的满子图,这时候加入 $x$ 和它相邻的边,显然此时不满足条件。
假设 $G'$ 中同时包含 $a, b$ 的导出子图都不是满子图,那么在 $G'$ 中加入边 $(a,b)$ 得到 $G''$,然后根据归纳假设有 $G''$ 是丛林。因此我们得到了两个森林,在包含 $(a, b)$ 的边的森林中,删除 $(a, b)$,然后加入 $(x, a), (x, b)$,再在另外一边加入 $(x, c)$。
剩下的问题是判断是否存在一个点集 $V$,它导出的边集 $E$,满足 $\frac{|E|}{|V| - 1} > 2$。
直接最大权闭合子图有点问题,因为它可能 $|V|$ 选空集,然后这样就会 GG。
每次硬点一个点必须选,退流就可以了。
时间复杂度 $O(nm)$。
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; const int inf = (signed) (~0u >> 2); typedef class Edge { public: int ed, nx, r; Edge() { } Edge(int ed, int nx, int r) : ed(ed), nx(nx), r(r) { } } Edge; typedef class MapManager { public: vector<int> h; vector<Edge> E; MapManager() { } MapManager(int n) { h.assign(n + 1, -1); } void add_edge(int u, int v, int c) { E.emplace_back(v, h[u], c); h[u] = (signed) E.size() - 1; } Edge& operator [] (int p) { return E[p]; } } MapManager; typedef class Network { public: int S, T; MapManager g; vector<int> div; int rest_flow; Network() { } Network(int S, int T) : S(S), T(T), g(T + 1) { div.resize(T + 1); } bool bfs() { queue<int> Q; fill(div.begin(), div.end(), -1); Q.push(S); div[S] = 0; while (!Q.empty()) { int p = Q.front(); Q.pop(); if (p == T) { continue; } for (int i = g.h[p]; ~i; i = g[i].nx) { int e = g[i].ed; if (!g[i].r || ~div[e]) { continue; } div[e] = div[p] + 1; Q.push(e); } } return div[T] != -1; } vector<int> cur; int dfs(int p, int mf) { if (p == T || !mf) { rest_flow -= mf; if (!rest_flow) { throw 1; } return mf; } int flow = 0, f; for (int& i = cur[p]; ~i; i = g[i].nx) { int e = g[i].ed; if (div[e] == div[p] + 1 && (f = dfs(e, min(mf, g[i].r))) > 0) { flow += f; g[i].r -= f; g[i ^ 1].r += f; mf -= f; if (!mf) { break; } } } return flow; } int dinic() { int ret = 0; try { while (bfs()) { cur = g.h; ret += dfs(S, inf); } } catch (int) { return 2; } return ret; } void add_edge(int u, int v, int c) { g.add_edge(u, v, c); g.add_edge(v, u, 0); } vector<bool> get_S() { vector<bool> vis (T + 1, false); queue<int> Q; Q.push(S); vis[S] = true; while (!Q.empty()) { int p = Q.front(); Q.pop(); for (int i = g.h[p]; ~i; i = g[i].nx) { int e = g[i].ed; if (!vis[e] && g[i].r) { vis[e] = true; Q.push(e); } } } return vis; } } Network; int n, m, lim; int main() { scanf("%d%d", &n, &m); lim = 2; int T; Network network(0, T = n + m + 1); vector<int> deg (n + 1, 0); vector<pair<int, int>> E (m); for (int i = 0, u, v; i < m; i++) { scanf("%d%d", &u, &v); if (u == v) { puts("No"); return 0; } E[i] = make_pair(u, v); ++deg[u]; ++deg[v]; } for (int i = 1; i <= n; i++) { if (deg[i] >= lim) { network.add_edge(i, T, lim); } } int cost = 0; for (int i = 1, u, v; i <= m; i++) { u = E[i - 1].first, v = E[i - 1].second; if (deg[u] >= lim && deg[v] >= lim) { network.add_edge(0, n + i, 1); network.add_edge(n + i, u, inf); network.add_edge(n + i, v, inf); ++cost; } } network.rest_flow = inf; cost -= network.dinic(); if (cost) { puts("No"); return 0; } else { auto inS = network.get_S(); for (int i = 1; i <= n; i++) { if (inS[i] && deg[i] >= lim) { puts("No"); return 0; } } for (int i = 1; i <= n; i++) { if (deg[i] < lim) { continue; } Network network1 = network; network1.add_edge(0, i, lim); network1.rest_flow = 2; int xx = network1.dinic(); if (xx < lim) { puts("No"); return 0; } } } puts("Yes"); return 0; }
Problem C 元旦老人与数列
若干 segment tree beats 板题之一。
考虑 segment tree beats 本质上是对区间的最小值打标记。当最小值集合发生改变的时候暴力递归。
所以对最小值和非区间最小值分别维护区间加标记。
因为要查询区间历史最小值,所以再维护一下历史最小值,以及最小值和非最小值的加标记的最小前缀和。
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 (stdin); 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 (stdout); const int N = 5e5 + 5; const int inf = (~0u >> 1); #define ll long long typedef class SegTreeNode { public: int mi, mi2; int tga, tga2; int hmi, mtga, mtga2; SegTreeNode *l, *r; void init(int x) { mi = x; mi2 = inf; hmi = x; tga = tga2 = mtga = mtga2 = 0; l = r = NULL; } void upd(int _tga, int _tga2, int _mtga, int _mtga2) { hmi = min(hmi, mi + _mtga); mi += _tga; (mi2 != inf) && (mi2 += _tga2, 0); mtga = min(mtga, tga + _mtga); mtga2 = min(mtga2, tga2 + _mtga2); tga += _tga; tga2 += _tga2; } void upd(int v) { if (v >= mi2) { push_down(); l->upd(v); r->upd(v); push_up(); } else if (v > mi) { tga += v - mi; mtga = min(mtga, tga); mi = v; } } void push_up() { hmi = min(l->hmi, r->hmi); mi = min(l->mi, r->mi); if (l->mi == r->mi) { mi2 = min(l->mi2, r->mi2); } else if (l->mi < r->mi) { mi2 = min(l->mi2, r->mi); } else { mi2 = min(l->mi, r->mi2); } } void push_down() { if (tga || tga2 || mtga || mtga2) { if (l->mi + tga == mi) { l->upd(tga, tga2, mtga, mtga2); } else { l->upd(tga2, tga2, mtga2, mtga2); } if (r->mi + tga == mi) { r->upd(tga, tga2, mtga, mtga2); } else { r->upd(tga2, tga2, mtga2, mtga2); } tga = tga2 = mtga = mtga2 = 0; } } } SegTreeNode; SegTreeNode pool[N << 1]; SegTreeNode* _top = pool; typedef class SegmentTree { public: int n; SegTreeNode* rt; void build(SegTreeNode*& p, int l, int r, int* a) { p = _top++; if (l == r) { p->init(a[l]); } else { int mid = (l + r) >> 1; build(p->l, l, mid, a); build(p->r, mid + 1, r, a); p->push_up(); } } void build(int n, int* a) { this->n = n; build(rt, 1, n, a); } void update_add(SegTreeNode* p, int l, int r, int ql, int qr, int a) { if (l == ql && r == qr) { p->upd(a, a, a, a); return; } p->push_down(); int mid = (l + r) >> 1; if (qr <= mid) { update_add(p->l, l, mid, ql, qr, a); } else if (ql > mid) { update_add(p->r, mid + 1, r, ql, qr, a); } else { update_add(p->l, l, mid, ql, mid, a); update_add(p->r, mid + 1, r, mid + 1, qr, a); } p->push_up(); } void update_add(int l, int r, int a) { update_add(rt, 1, n, l, r, a); } void update_mx(SegTreeNode* p, int l, int r, int ql, int qr, int v) { if (l == ql && r == qr) { p->upd(v); return; } p->push_down(); int mid = (l + r) >> 1; if (qr <= mid) { update_mx(p->l, l, mid, ql, qr, v); } else if (ql > mid) { update_mx(p->r, mid + 1, r, ql, qr, v); } else { update_mx(p->l, l, mid, ql, mid, v); update_mx(p->r, mid + 1, r, mid + 1, qr, v); } p->push_up(); } void update_mx(int l, int r, int v) { update_mx(rt, 1, n, l, r, v); } int query_mi(SegTreeNode* p, int l, int r, int ql, int qr) { if (l == ql && r == qr) { return p->mi; } p->push_down(); int mid = (l + r) >> 1; if (qr <= mid) { return query_mi(p->l, l, mid, ql, qr); } else if (ql > mid) { return query_mi(p->r, mid + 1, r, ql, qr); } int a = query_mi(p->l, l, mid, ql, mid); int b = query_mi(p->r, mid + 1, r, mid + 1, qr); return min(a, b); } int query_mi(int l, int r) { return query_mi(rt, 1, n, l, r); } int query_hmi(SegTreeNode* p, int l, int r, int ql, int qr) { if (l == ql && r == qr) { return p->hmi; } p->push_down(); int mid = (l + r) >> 1; if (qr <= mid) { return query_hmi(p->l, l, mid, ql, qr); } else if (ql > mid) { return query_hmi(p->r, mid + 1, r, ql, qr); } int a = query_hmi(p->l, l, mid, ql, mid); int b = query_hmi(p->r, mid + 1, r, mid + 1, qr); return min(a, b); } int query_hmi(int l, int r) { return query_hmi(rt, 1, n, l, r); } } SegmentTree; int n, m; int a[N]; SegmentTree st; int main() { in >> n >> m; for (int i = 1; i <= n; i++) { in >> a[i]; } st.build(n, a); int op, l, r, x; while (m--) { in >> op >> l >> r; if (op <= 2) { in >> x; if (op == 1) { st.update_add(l, r, x); } else{ st.update_mx(l, r, x); } } else if (op == 3) { out << st.query_mi(l, r) << '\n'; } else { out << st.query_hmi(l, r) << '\n'; } } return 0; }