AtCoder Grand Contest 043 简要题解
从这里开始
Problem A Range Flip Find Route
考虑对于一条路径的答案是交错的次数除以 2 向上取整。 dp 即可。
Code
#include <bits/stdc++.h> using namespace std; const int N = 105; const int inf = 1e9 + 7; int H, W; char G[N][N]; int f[N][N]; int main() { scanf("%d%d", &H, &W); for (int i = 1; i <= H; i++) { scanf("%s", G[i] + 1); } f[1][1] = (G[1][1] != '.'); for (int s = 3; s <= H + W; s++) { for (int x = max(s - W, 1); x <= H && x < s; x++) { int y = s - x; f[x][y] = 1e9 + 7; if (x > 1) f[x][y] = min(f[x][y], f[x - 1][y] + (G[x - 1][y] != G[x][y])); if (y > 1) f[x][y] = min(f[x][y], f[x][y - 1] + (G[x][y - 1] != G[x][y])); } } int ans = ((G[H][W] != '.') + f[H][W] + 1) >> 1; printf("%d\n", ans); return 0; }
Problem B 123 Triangle
你可以根据第一轮差判断答案是否可能是 2。如果是 2,说明第一轮差分后只有 0 和 2,把 2 当成 1。
然后问题都相当于判断模 2 最后一位是否是 1。
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; const int N = 1e6 + 5; int n; char s[N]; int main() { scanf("%d", &n); scanf("%s", s + 1); boolean flag2 = true; for (int i = 1; i < n; i++) { if (s[i + 1] >= s[i]) { s[i] = s[i + 1] - s[i]; } else { s[i] = s[i] - s[i + 1]; } if (s[i] == 1) { flag2 = false; } } if (flag2) { for (int i = 1; i < n; i++) { if (s[i] == 2) { s[i] = 1; } } } else { for (int i = 1; i < n; i++) { if (s[i] == 2) { s[i] = 0; } } } int ans = 0; --n; for (int i = 0; i < n; i++) { if ((i & (n - 1)) == i) { ans += s[i + 1]; } } ans = (ans & 1) * (1 + flag2); printf("%d\n", ans); return 0; }
Problem C Giant Graph
注意到这个标记的过程和博弈有点像。考虑这样一个问题,Alice 和 Bob 轮流移动有向图上的一个棋子。
然后相信大家都会了。
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; } friend bool 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; Zi base (1000000000000000000ll); Zi pw[N]; void init_pw(int n) { pw[0] = 1; pw[1] = base; for (int i = 2; i <= n; i++) { pw[i] = pw[i - 1] * base; } } int n; typedef class Graph { public: bitset<512> vis; int SG[N]; Zi buk[512]; vector<int> G[N]; Zi* solve() { int u, v, m; scanf("%d", &m); while (m--) { scanf("%d%d", &u, &v); if (u > v) { swap(u, v); } G[u].push_back(v); } for (int i = n; i; i--) { for (auto e : G[i]) { vis.set(SG[e]); } while (vis.test(SG[i])) SG[i]++; for (auto e : G[i]) { vis.reset(SG[e]); } } for (int i = 1; i <= n; i++) { buk[SG[i]] += pw[i]; } return buk; } } Graph; Graph g1, g2, g3; void fwt(Zi* f, int n) { int N = 1 << n; for (int i = 0; i < n; i++) { for (int j = 0; j < N; j++) { if ((j >> i) & 1) { Zi a = f[j ^ (1 << i)], b = f[j]; f[j ^ (1 << i)] = a + b; f[j] = a - b; } } } } void ifwt(Zi* f, int n) { fwt(f, n); int N = 1 << n; Zi invlen = ~Zi(N); for (int i = 0; i < N; i++) { f[i] *= invlen; } } int main() { scanf("%d", &n); init_pw(n); Zi* b1 = g1.solve(); Zi* b2 = g2.solve(); Zi* b3 = g3.solve(); fwt(b1, 9); fwt(b2, 9); fwt(b3, 9); for (int i = 0; i < 512; i++) { b1[i] = b1[i] * b2[i] * b3[i]; } ifwt(b1, 9); printf("%d\n", b1->v); return 0; }
Problem D Merge Triplets
考虑最小数,显然它必须塞进前面的数所在序列中,然后把它删掉。
因此条件是:
- 考虑前缀最大值,两个前缀最大值间的数小于等于 2
- 长度为 1 的段的数量大于等于长度为 2 的段的数量。
然后 dp 即可。
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; const int N = 6005; #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); } int 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; } }; typedef Z Zi; int n; Zi Inv[N]; Zi f[N][6015]; int main() { scanf("%d%d", &n, &Mod); const int off = 3005; f[0][off] = 1; Inv[0] = 0, Inv[1] = 1; int n3 = n * 3; for (int i = 2; i <= n3; i++) { Inv[i] = -Inv[Mod % i] * (Mod / i); } for (int i = 0; i < n3; i++) { int L = -(i >> 1), R = min(i, 3005); for (int j = L; j <= R; j++) { Zi v = f[i][j + off]; if (!v.v) { continue; } v *= Inv[n3 - i]; f[i + 3][j + off] += v; f[i + 2][j - 1 + off] += v; f[i + 1][min(j + 1, off) + off] += v; } } Zi ans = 0; for (int i = 0; i <= min(off, n * 3); i++) { ans += f[n3][i + off]; } for (int i = 1; i <= n3; i++) ans *= i; printf("%d\n", ans.v); return 0; }
Problem E Topology
考虑如何判断一个在某个点集下是否能将线圈移动到 $y$ 轴下方。
考虑沿着线圈走,如果经过第 $i$ 个点向上的射线那么记下 $u_i$,如果经过第 $i$ 个点向下的射线,那么记下 $d_i$。能移动的条件是存在一种方案每次删除相邻的相同字符使得字符串变为空。
证明?因为我不会所以这里没有。
注意到一个显然的必要条件是如果 $A_S = 1$,对于任意 $T \subset S$ 也满足 $A_T = 1$。可以证明这是充分的。
考虑怎么构造 $1111111\cdots110$。设对于 $n = k$,构造的字符串为 $a_k$。
显然当 $n = 1$ 的时候令 $a_1 = u_1d_1$ 就可以了。
考虑 $n > 1$ 的情况,令 $a_n = u_n a_{n - 1} u_n d_n \overline{a_{n-1}} d_n$
如果 $n$ 不在点集内,那么显然能消空,否则 $a_{n - 1}$ 不能消除当且仅当 $1, 2, \cdots, n - 1$ 中某个点不存在。这恰好是我们想要的。
然后对于每个 $A_S = 0$ 把这样一个东西放上去就行了。
Code
#include <bits/stdc++.h> using namespace std; template <typename T> vector<T>& operator += (vector<T>& a, vector<T> b) { for (auto x : b) { a.push_back(x); } return a; } template <typename T> vector<T> operator ~ (vector<T> a) { reverse(a.begin(), a.end()); return a; } typedef class Point { public: int x, y; Point() { } Point(int x, int y) : x(x), y(y) { } bool operator == (Point b) { return x == b.x && y == b.y; } } Point; typedef class Data { public: char dir; int num; Data() { } Data(char dir, int num) : dir(dir), num(num) { } } Data; int n, N; vector<Point> _export(vector<Data> vd) { vector<Point> ret {Point(n, 1)}; for (auto t : vd) { auto lst = ret.back(); if (t.dir == 'd' && lst.y == 1) { ret.emplace_back(lst.x, 0); } else if (t.dir == 'u' && lst.y == 0) { ret.emplace_back(lst.x, 1); } lst = ret.back(); if (lst.x == t.num) { lst.x--; } else if (lst.x == t.num - 1) { lst.x++; } else { assert(false); } ret.push_back(lst); } if (ret[0] == ret.back()) { ret.pop_back(); } return ret; } vector<Point> _export(vector<Data> vd, vector<int> pos) { for (auto& t : vd) { t.num = pos[t.num - 1] + 1; } vector<Data> nvd; for (int i = n; i > vd[0].num; i--) { nvd.emplace_back('d', i); } for (auto t : vd) { if (!nvd.empty()) { auto& lst = nvd.back(); for (int i = lst.num + 1; i < t.num; i++) { nvd.emplace_back('d', i); } for (int i = lst.num - 1; i > t.num; i--) { nvd.emplace_back('d', i); } } nvd.push_back(t); } for (int i = nvd.back().num + 1; i <= n; i++) { nvd.emplace_back('d', i); } return _export(nvd); } char A[530]; vector<Data> df[10]; #define NO do puts("Impossible"), exit(0); while (0) int main() { scanf("%d", &n); N = 1 << n; scanf("%s", A); if (A[0] != '1') { NO; } for (int s = 0; s < N; s++) { if (A[s] == '1') { for (int i = 0; i < n; i++) { if (((s >> i) & 1) && A[s ^ (1 << i)] == '0') { NO; } } } } df[1].emplace_back('d', 1); df[1].emplace_back('u', 1); for (int i = 2; i <= n; i++) { df[i].emplace_back('u', i); df[i] += df[i - 1]; df[i].emplace_back('u', i); df[i].emplace_back('d', i); df[i] += ~df[i - 1]; df[i].emplace_back('d', i); } vector<Point> ans; for (int s = 1; s < N; s++) { if (A[s] == '0') { vector<int> pos; int bit = 0; for (int i = 0; i < n; i++) { if ((s >> i) & 1) { pos.push_back(i); bit++; } } ans += _export(df[bit], pos); } } puts("Possible"); printf("%d\n", (signed) ans.size()); if (ans.empty()) { ans.emplace_back(0, 0); } else { ans.push_back(ans[0]); } for (auto p : ans) { printf("%d %d\n", p.x, p.y); } return 0; }
Problem F Jewelry Box
通常情况下,某位大神会跑来给我说这个 F 对个偶就没了,并表示错过了吊打 tourist 的机会非常地气。
先考虑 $Q = 1$ 的情形,不妨设询问的是 $A$。
考虑怎么判断每个商店选出若干珠宝是否合法。考虑每次贪心地选择每个商店重量最小的珠宝凑成珠宝盒。
注意到如果在 $v_i$ 商店购买了 $x$ 个重量小于等于 $k$ 的珠宝,那么要求在 $u_i$ 商店购买了至少 $A - x$ 个重量大于等于 $k - w_i$ 的物品。
不难用上面的贪心证明这个条件是充分的。
将第 $i$ 个商店的商品按重量从小到达排序,并设前 $j$ 个商品中购买了 $x_{i, j}$ 个。
大概有如下限制:
- $x_{i, 0} = 0$
- $x_{i, K_i} = A$
- $x_{i, j} \geqslant x_{i, j - 1}$
- $x_{i, j} - x_{i, j - 1} \leqslant C_{i, j}$
- $x_{v_i, j} \geqslant x_{u_i, k}$
然后要最小化 $\sum (x_{i, j} - x_{i, j - 1})P_{i, j}$。
首先把它标准化,再对偶,然后就是个上下界最大费用可行流问题。不难注意到你可以通过一些增广操作使得只经过 0 边让有下界的边都满流。现在已经最大流了,只能通过正环改变费用了。为了方便你需要把 $x_{i, 0}$ 以及 $x_{i, K_i}$ 分别压成一个点,这样和 $A$ 相关的边只有两条了。注意到 $x_{i, K_i} \leqslant A$ 是没有意义的,因为是最小化费用,多选了肯定不优。因此和 $A$ 相关的边只有一条了。
注意到正环一定包含费用为 $A$ 这条边,如果包含了至少一次说明存在正环,原问题要么是 infeasible 要么是 unbounded。所以在有最优解的情况下,每个正环恰好包含一次费用为 $A$ 的边。把它删掉,剩下变成从 $x_{i, K_i}$ 到 $x_{i, 0}$ 的一条路径。
然后跑费用流预处理出凸包,然后询问的时候二分一下就行了。
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; #define ll long long const ll llf = (signed ll) (~0ull >> 3); template <typename T> void pfill(T* pst, const T* ped, T val) { for ( ; pst != ped; *(pst++) = val); } typedef class Edge { public: int ed, nx; ll r, c; Edge() { } Edge(int ed, int nx, ll r, ll c) : ed(ed), nx(nx), r(r), c(c) { } } Edge; typedef class MapManager { public: int* h; vector<Edge> es; MapManager() { } MapManager(int n) { h = new int[(n + 1)]; memset(h, -1, (n + 1) << 2); } void add_edge(int u, int v, ll cap, ll c) { es.emplace_back(v, h[u], cap, c); h[u] = (signed) es.size() - 1; } void add_arc(int u, int v, ll cap, ll c) { add_edge(u, v, cap, c); add_edge(v, u, 0, -c); } Edge& operator [] (int p) { return es[p]; } } MapManager; typedef class Data { public: ll f, c, res; Data(ll f, ll c, ll res) : f(f), c(c), res(res) { } } Data; typedef class Network { public: int S, T; MapManager g; Network() { } Network(int S, int T) : S(S), T(T), g(T) { f = new ll[(T + 1)]; mf = new ll[(T + 1)]; le = new int[(T + 1)]; vis = new boolean[T + 1]; } ll *f, *mf; int *le; boolean *vis; void clear() { delete[] f; delete[] le; delete[] mf; delete[] vis; } vector<Data> conv; boolean spfa() { queue<int> Q; pfill(f, f + T + 1, llf); pfill(vis, vis + T + 1, false); Q.push(S); f[S] = 0, mf[S] = llf, le[S] = -1; while (!Q.empty()) { int e = Q.front(); Q.pop(); vis[e] = false; for (int i = g.h[e], eu; ~i; i = g[i].nx) { eu = g[i].ed; if (!g[i].r) continue; ll w = f[e] + g[i].c; if (w < f[eu]) { f[eu] = w, mf[eu] = min(mf[e], g[i].r), le[eu] = i; if (!vis[eu]) { Q.push(eu); vis[eu] = true; } } } } if (f[T] == llf) return false; ll flow = mf[T]; for (int p = T, e; p ^ S; p = g[le[p] ^ 1].ed) { e = le[p]; g[e].r -= flow; g[e ^ 1].r += flow; } Data t = conv.back(); conv.emplace_back(t.f + flow, f[T], t.res + 1ll * f[T] * flow); if (t.f + flow >= (1ll << 50)) { return false; } return true; } void min_cost() { conv.emplace_back(0, 0, 0); while (spfa()); } void add_edge(int u, int v, ll cap, ll w) { // cerr << u << "->" << v << " " << cap << " " << w << '\n'; g.add_arc(u, v, cap, w); } } Network; typedef class Jewelry { public: int s, p; ll c; void read() { cin >> s >> p >> c; } boolean operator < (Jewelry b) const { return s < b.s; } } Jewelry; int n, m, q; vector<vector<int>> id; vector<vector<Jewelry>> shop; int main() { ios::sync_with_stdio(false); cin.tie(0), cout.tie(0); cin >> n; id.resize(n); shop.resize(n); int T = 0; for (int i = 0, K; i < n; i++) { cin >> K; id[i].resize(K - 1); shop[i].resize(K); for (auto& x : id[i]) { x = ++T; } id[i].push_back(-1); id[i].insert(id[i].begin(), 0); for (auto& x : shop[i]) { x.read(); } sort(shop[i].begin(), shop[i].end()); } Network network (0, ++T); for (auto& v : id) { v.back() = T; } for (int i = 0; i < n; i++) { int K = shop[i].size(); for (int j = 0; j < K; j++) { network.add_edge(id[i][j], id[i][j + 1], shop[i][j].p, 0); network.add_edge(id[i][j + 1], id[i][j], llf, 0); network.add_edge(id[i][j], id[i][j + 1], llf, shop[i][j].c); } } cin >> m; for (int i = 0, x, y, w; i < m; i++) { cin >> x >> y >> w; --x, --y; int kx = shop[x].size(), ky = shop[y].size(); for (int l = 0, r = 0; l < ky; l++) { while (r < kx && shop[y][l].s - w > shop[x][r].s) { r++; } network.add_edge(id[y][l], id[x][r], llf, 0); } } network.min_cost(); auto& conv = network.conv; cin >> q; ll a; while (q--) { cin >> a; auto t = lower_bound(conv.begin(), conv.end(), a, [&] (Data x, ll y) { return x.c < y; }); if (t == conv.end()) { cout << "-1" << '\n'; } else { --t; cout << 1ll * (*t).f * a - (*t).res << '\n'; } } return 0; }