从这里开始
我菜爆了。
Problem A As Simple as One and Two
我会 AC 自动机上 dp。
one 和 two 删掉中间的字符,twone 删掉中间的 o。
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; const int N = 2e5 + 5; template <typename T> boolean vmin(T& a, T b) { return (a > b) ? (a = b, true) : false; } int T, n; char s[N]; boolean check(const char* t, const char* s) { while (*t && *s) if (*t ^ *s) return false; else t++, s++; return !*s; } void solve() { vector<int> S; for (int i = 1; i <= n; ) { if (check(s + i, "twone")) { S.push_back(i + 2); i += 5; } else if (check(s + i, "one") || check(s + i, "two")) { S.push_back(i + 1); i += 3; } else { i++; } } printf("%d\n", (signed) S.size()); for (auto x : S) { printf("%d ", x); } putchar('\n'); } int main() { scanf("%d", &T); while (T--) { scanf("%s", s + 1); n = strlen(s + 1); solve(); } return 0; }
Problem B Two Fairs
考虑删掉 a, b 把连通块分为之和 a 相连以及之和 b 相连还有 同时和 a, b 相连,答案是前两类的点数之积。
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; const int N = 2e5 + 5, M = 5e5 + 5; #define ll long long int T; int n, m, a, b; int us[M], vs[M]; int uf[N]; int find(int x) { return (uf[x] == x) ? (x) : (uf[x] = find(uf[x])); } boolean ban[N]; ll solve() { for (int i = 1; i <= n; i++) uf[i] = i; for (int i = 1; i <= m; i++) { int x = us[i], y = vs[i]; if (ban[x] || ban[y]) continue; x = find(x), y = find(y); if (x ^ y) uf[x] = y; } int cnt = 0; for (int i = 1; i <= n; i++) { if (find(i) != find(a) && find(i) != find(b)) { cnt++; } } return cnt; } int main() { scanf("%d", &T); while (T--) { scanf("%d%d%d%d", &n, &m, &a, &b); for (int i = 1; i <= m; i++) { scanf("%d%d", us + i, vs + i); } ll ans = 0; ban[a] = true; ans += solve(); ban[a] = false, ban[b] = true; ans *= solve(); ban[a] = false, ban[b] = false; printf("%lld\n", ans); } return 0; }
Problem C Beautiful Rectangle
考虑枚举 $r$。不妨设 $r \leqslant c$。
不难发现必要条件是同一种数的数量不超过 $r$。
不难证明它是充分的,考虑按 $(1, 1), (2, 2), \cdots, (r, r), (1, 2), (2, 3), \cdots$ 的顺序填数,相同的数要连续填,先填出现次数为 $r$ 的数。
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; const int N = 4e5 + 5; #define pii pair<int, int> template <typename T> boolean vmax(T& a, T b) { return (a < b) ? (a = b, true) : false; } int n; int a[N]; vector<pii> b; int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) {\ scanf("%d", a + i); } sort(a + 1, a + n + 1); b.emplace_back(a[1], 1); for (int i = 2; i <= n; i++) { if (a[i] ^ b.back().first) { b.emplace_back(a[i], 1); } else { b.back().second++; } } for (auto &x : b) { swap(x.first, x.second); } sort(b.begin(), b.end()); int sum = 0, cnt = b.size(); int ans = 0, R, C; vector<pii>::iterator p = b.begin(), _p = b.end(); for (int r = 1, c; r <= n; r++) { while (p != _p && (*p).first <= r) { sum += (*p).first; cnt--; p++; } c = (sum + cnt * r) / r; if (c >= r && vmax(ans, r * c)) { R = r, C = c; } } printf("%d\n", ans); vector<vector<int>> mat(R, vector<int>(C, 0)); reverse(b.begin(), b.end()); int x = 0, y = 0; for (auto par : b) { int cnt = min(par.first, R); int v = par.second; while (ans && cnt) { mat[x][(x + y) % C] = v; if (++x >= R) x -= R, y++; ans--, cnt--; } } printf("%d %d\n", R, C); for (auto& a : mat) { for (auto v : a) { printf("%d ", v); } putchar('\n'); } return 0; }
Problem D Tree Elimination
不难注意到,不同的操作序列对应不同的结果,因此我们只用对操作序列进行计数。
大力讨论一条边 $(u, v)$ 的两端点之间的关系:
- $u$ 被删掉
- $v$ 被删掉
- 这条边不会被操作
- $u$ 早被删掉
- $v$ 早被删掉
然后 dp 即可。
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; #define pii pair<int, int> int n; Zi f[N][6]; vector<pii> G[N]; void dfs(int p, int fa) { static Zi g[6]; f[p][1] = 1; sort(G[p].begin(), G[p].end()); boolean aflag = false; for (auto _ : G[p]) { int e = _.second; if (e == fa) { Zi x = f[p][0], y = f[p][1]; f[p][0] = y; f[p][1] = 0; f[p][2] = y; f[p][3] = x; f[p][4] = 0; f[p][5] = y; aflag = true; } else if (!aflag) { dfs(e, p); Zi x = f[e][0], y = f[e][1] + f[e][2], z = f[e][3], w = f[e][4] + f[e][5]; g[0] = f[p][0] * (z + w) + f[p][1] * y; g[1] = f[p][1] * (x + z); f[p][0] = g[0], f[p][1] = g[1]; } else { dfs(e, p); Zi x = f[e][0], y = f[e][1] + f[e][2], z = f[e][3], w = f[e][4] + f[e][5]; g[0] = f[p][0] * (z + w); g[1] = f[p][1] * (z + w) + f[p][2] * y; g[2] = f[p][2] * (x + z); g[3] = f[p][3] * (z + w); g[4] = f[p][4] * (z + w) + f[p][5] * y; g[5] = f[p][5] * (x + z); memcpy(f[p], g, sizeof(g)); } } // cerr << p << " " << f[p][0].v << " " << f[p][1].v << " " << f[p][2].v << " " << f[p][3].v << '\n'; } int main() { scanf("%d", &n); for (int i = 1, u, v; i < n; i++) { scanf("%d%d", &u, &v); G[u].emplace_back(i, v); G[v].emplace_back(i, u); } dfs(1, 0); Zi ans = f[1][1] + f[1][0]; printf("%d\n", ans.v); return 0; }
Problem E Four Stones
不难发现 $(a_1, a_2, a_3, a_4)$ 能到的状态满足:
- $g = gcd(a_2 - a_1, a_3 - a_2, a_4 - a_3)$ 不变
- $a_1 \mod g$ 相同
- $a_i \equiv a'_i \pmod {2g}$
必要性比较显然。充分性也挺好证明的,考虑 3 个石子,$x, y, z\ (x \leqslant y \leqslant z)$,每次我们总可以通过 1 次操作让距离较大的相邻两点的距离减去距离较小的相邻两点的距离。然后可以利用这个东西做 $gcd$,把 $4$ 个石子变为两堆石子。后面的条件就比较好证明了。
考虑优化一下上述求 gcd 的做法,我来讲一下我的垃圾做法,不妨设四个点间,相邻两点之间的距离分别为 $d_1, d_2, d_3$
- 如果它们之中的最大值为 $d_1$ 或者 $d_3$,不妨设为 $d_1$
- 如果 $2(d_2 + d_3) \leqslant d_1$,那么把最后一个点以第二个点为中心对称。
- 否则把第一个点以第二个点为中心对称
- 如果是 $d_2$
- 如果两边较长一段的长度小于中间的一段的一半,那么可以通过两次操作使得总长度不增,并且较短的长度加上二倍较长的长度
- 否则把较长的一段往中间折。
显然每 $O(\log V)$ 次操作可以使得总长度减少至少 $\frac{1}{4}$。
考虑怎么快速移动两堆数。考虑先把它变为 3 堆,然后利用 fibonacci 简单构造一下似乎就行了?具体就是考虑能走就走,否则把它变为 $(f_{n - 3} g, f_{n - 2} g)$。
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; #define ll long long const ll llf = (signed ll) (~0ull >> 1); template <typename T> T smax(T x) { return x; } template <typename T, typename ...K> T smax(T x, K ...args) { return max(x, smax(args...)); } ll gcd(ll a, ll b) { return (b) ? gcd(b, a % b) : a; } int count_dif(vector<ll>& a) { sort(a.begin(), a.end()); ll x = -llf; int ret = 0; for (auto y : a) { if (y ^ x) { x = y; ret++; } } return ret; } void calc(vector<ll> a, ll& g, ll& first, int& cnt) { sort(a.begin(), a.end()); ll d1 = a[1] - a[0], d2 = a[2] - a[0], d3 = a[3] - a[0]; g = gcd(d1, gcd(d2, d3)); if (!g) { first = a[0]; cnt = 0; return; } cnt = 0; first = (a[0] % g + g) % g; for (auto x : a) cnt += ((x - first) / g) & 1; } vector<pair<ll, ll>> solve(vector<ll> a) { vector<pair<ll, ll>> ret; ll g, fr; int _; calc(a, g, fr, _); auto work = [&] (int i, int j) { if (a[i] == a[j]) return; ret.emplace_back(a[i], a[j]); // cerr << "D " << a[i] << " " << a[j] << '\n'; a[i] = a[j] * 2 - a[i]; }; while (count_dif(a) > 2) { ll d1 = a[1] - a[0], d2 = a[2] - a[1], d3 = a[3] - a[2]; ll mxd = smax(d1, d2, d3); // cerr << d1 << " " << d2 << " " << d3 << '\n'; if (d1 == mxd) { if (a[1] * 2 - a[0] <= a[3]) { work(0, 1); } else { if ((d3 + d2) * 2 <= d1) { work(3, 1); } else { work(0, 1); } } } else if (d2 == mxd) { if (d1 > d3) { if (d2 > (d1 << 1)) { work(2, 1); work(2, 0); } else { work(0, 1); } } else { if (d2 > (d3 << 1)) { work(1, 2); work(1, 3); } else { work(3, 2); } } } else { if (a[2] * 2 - a[3] >= a[0]) { work(3, 2); } else { if (2 * (d1 + d2) < d3) { work(0, 2); } else { work(3, 2); } } } } vector<vector<int>> grp; auto get_grp = [&] (vector<ll>& a) { grp.clear(); sort(a.begin(), a.end()); grp.push_back({0}); for (int i = 1; i < 4; i++) if (a[i] == a[i - 1]) grp.back().push_back(i); else grp.push_back({i}); }; auto work_grp = [&] (int i, int j) { for (auto x : grp[i]) { work(x, grp[j][0]); } }; if (a[0] < 0) { work(0, 3); if (count_dif(a) == 2 && a[0] == fr) return ret; while (count_dif(a) <= 2) work(0, 3); while (get_grp(a), fr - a[3] > 5 * g || (a[3] + a[grp[2][0]] - a[grp[1][0]] >= fr && a[grp[2][0]] - a[grp[1][0]] >= 3 * g)) { ll d1 = a[grp[1][0]] - a[0], d2 = a[grp[2][0]] - a[grp[1][0]]; // cerr << d1 << " " << d2 << '\n'; assert(d1 <= d2); assert(a[0] < fr); if (a[3] + d1 + d2 < fr) { work_grp(0, 2); } else if (a[3] + d2 < fr) { work_grp(0, 2); work_grp(1, 2); work_grp(0, 1); } else { work_grp(0, 1); work_grp(2, 0); } } while (get_grp(a), true) { ll d1 = a[grp[1][0]] - a[0], d2 = a[grp[2][0]] - a[grp[1][0]]; if (d1 == d2) { work_grp(0, 1); break; } else if (d1 > d2) { work_grp(2, 1); } else { work_grp(0, 1); } } while (get_grp(a), a[0] != fr) { work_grp(0, 1); } } else if (a[0] >= g) { work(3, 0); if (count_dif(a) == 2 && a[0] == fr) return ret; while (count_dif(a) <= 2) work(3, 0); while (get_grp(a), a[0] - fr > 5 * g || (a[0] + a[grp[1][0]] - a[0] <= fr && a[grp[1][0]] - a[0] >= 3 * g)) { ll d1 = a[grp[1][0]] - a[0], d2 = a[grp[2][0]] - a[grp[1][0]]; // cerr << d1 << " " << d2 << '\n'; assert(d1 >= d2); if (a[0] - d1 - d2 > fr) { work_grp(2, 0); } else if (a[0] - d1 > fr) { work_grp(2, 0); work_grp(1, 0); work_grp(2, 1); } else { work_grp(2, 1); work_grp(0, 2); } } while (get_grp(a), true) { ll d1 = a[grp[1][0]] - a[0], d2 = a[grp[2][0]] - a[grp[1][0]]; if (d1 == d2) { work_grp(2, 1); break; } else if (d1 > d2) { work_grp(2, 1); } else { work_grp(0, 1); } } while (get_grp(a), a[0] != fr) { work_grp(1, 0); } } return ret; } void read(vector<ll>& a) { a.resize(4); for (int i = 0; i < 4; i++) scanf("%lld", a.data() + i); } int main() { vector<ll> A, B; read(A); read(B); ll ga, fa, gb, fb; int ca, cb; calc(A, ga, fa, ca); calc(B, gb, fb, cb); if ((ga ^ gb) || (fa ^ fb) || (ca ^ cb)) { puts("-1"); return 0; } if (!ga) { puts("0"); return 0; } vector<pair<ll, ll>> retA = solve(A); vector<pair<ll, ll>> retB = solve(B); reverse(retB.begin(), retB.end()); printf("%d\n", (signed) (retA.size() + retB.size())); for (auto x : retA) printf("%lld %lld\n", x.first, x.second); for (auto x : retB) printf("%lld %lld\n", 2 * x.second - x.first, x.second); return 0; }
Problem F Asterisk Substrings
这题是真白给的。感觉错过了良心上分场?
考虑只用计算新增的形如 $s \ast t$ 的数量。
枚举一下 $s$ 在反前缀树上在哪个点,不同的 $t$ 的数量大概是后缀树上的链并。
第一棵树上 dsu on tree,第二棵树上维护链并就没了。
感觉加上 finger-search 和 O(1) 维护前驱后继就能 1 个 log 了。
Code
#include <bits/stdc++.h> using namespace std; typedef bool boolean; #define ll long long #define pii pair<int, int> const int N = 1e5 + 5, N2 = N << 1; typedef class TrieNode { public: int len; TrieNode *ch[26]; TrieNode *par; boolean leaf; TrieNode() : len(0), par(NULL), leaf(false) { memset(ch, 0, sizeof(ch)); } } TrieNode; typedef class SuffixAutomaton { public: TrieNode pool[N << 1]; TrieNode *_top; TrieNode *sam_rt, *sam_lst; SuffixAutomaton() : _top(pool) { sam_rt = newnode(0); sam_lst = sam_rt; } TrieNode *newnode(int len) { _top->len = len; return _top++; } int extend(int c) { TrieNode *cur = newnode(sam_lst->len + 1); TrieNode *p = sam_lst; while (p && !p->ch[c]) p->ch[c] = cur, p = p->par; if (!p) { cur->par = sam_rt; } else { TrieNode* q = p->ch[c]; if (q->len == p->len + 1) { cur->par = q; } else { TrieNode* nq = newnode(p->len + 1); memcpy(nq->ch, q->ch, sizeof(q->ch)); nq->par = q->par, q->par = nq, cur->par = nq; while (p && p->ch[c] == q) p->ch[c] = nq, p = p->par; } } sam_lst = cur; cur->leaf = true; return sam_lst - pool + 1; } int build(vector<int>* G, int* fa, int* df) { int K = _top - pool; df[1] = 1; for (int i = 1; i < K; i++) { fa[i + 1] = pool[i].par - pool + 1; df[i + 1] = pool[i].len + 1; G[fa[i + 1]].push_back(i + 1); } return K; } } SuffixAutomaton; const int bzmax = 19; const int N4 = N2 << 1; template <typename T> class SparseTable { public: int n; int Log2[N4]; T st[bzmax][N4]; void init(int n, T* a) { Log2[0] = -1; for (int i = 1; i <= n; i++) { Log2[i] = Log2[i >> 1] + 1; } for (int i = 1; i <= n; i++) { st[0][i] = a[i]; } for (int i = 1; i < bzmax; i++) { for (int j = 1; j + (1 << i) - 1 <= n; j++) { st[i][j] = min(st[i - 1][j], st[i - 1][j + (1 << (i - 1))]); } } } T query(int l, int r) { int b = Log2[r - l + 1]; return min(st[b][l], st[b][r - (1 << b) + 1]); } }; typedef class Tree { public: int n; int dfc; int dep[N2]; pii seq[N4]; vector<int>* G; int in[N2], out[N2]; SparseTable<pii> st; void dfs1(int p, int fa) { in[p] = ++dfc; dep[p] = dep[fa] + 1; seq[dfc] = pii(dep[p], p); for (auto e : G[p]) { if (e ^ fa) { dfs1(e, p); seq[++dfc] = pii(dep[p], p); } } out[p] = dfc; } int _lca(int l, int r) { return st.query(l, r).second; } int lca(int u, int v) { (in[u] > in[v]) && (swap(u, v), 0); return _lca(in[u], in[v]); } void init(int n, vector<int>* G) { this->G = G; this->n = n; dfc = 0; dfs1(1, 0); st.init(dfc, seq); } } Tree; vector<int> G1[N2]; int fa1[N2], df1[N2]; Tree tr; vector<int> G2[N2]; int fa2[N2], df2[N2]; int dfc; int in[N2], tour[N2]; void dfs(int p) { in[p] = ++dfc; tour[in[p]] = p; for (auto e : G2[p]) { if (e ^ fa2[p]) { dfs(e); } } } typedef class VirtualTree { public: ll res; set<int> S; int get_pre(int x) { set<int>::iterator it = S.lower_bound(x); if (it == S.begin()) return -1; return *--it; } int get_suf(int x) { set<int>::iterator it = S.upper_bound(x); if (it == S.end()) return -1; return *it; } void insert(int p); int size() { return S.size(); } } VirtualTree; #define lca tr.lca void VirtualTree::insert(int p) { int x = in[p]; int l = get_pre(x), r = get_suf(x); res += df2[p]; if (l != -1 && r != -1) { int g = lca(tour[l], tour[r]); res += df2[g]; } if (l != -1) { int g = lca(tour[l], p); res -= df2[g]; } if (r != -1) { int g = lca(p, tour[r]); res -= df2[g]; } S.insert(x); } #undef lca int n; ll ans = 0; char s[N]; VirtualTree vt[N2]; int id1[N], id2[N]; SuffixAutomaton saml, samr; void merge(VirtualTree& a, VirtualTree& b) { if (a.size() < b.size()) swap(a, b); for (auto x : b.S) { a.insert(tour[x]); } } void solve(int p) { for (auto e : G1[p]) { solve(e); merge(vt[p], vt[e]); } ans += (df1[p] - df1[fa1[p]]) * (vt[p].res + 1); } int main() { scanf("%s", s + 1); n = strlen(s + 1); id1[0] = 1; for (int i = 1; i <= n; i++) { id1[i] = saml.extend(s[i] - 'a'); } id2[n + 1] = 1; for (int i = n; i; i--) { id2[i] = samr.extend(s[i] - 'a'); } saml.build(G1, fa1, df1); tr.init(samr.build(G2, fa2, df2), G2); dfs(1); for (int i = 0; i < n; i++) { vt[id1[i]].insert(id2[i + 2]); } solve(1); printf("%lld\n", ans); return 0; }