【比赛链接】
【题解链接】
**【A】**Gennady and a Card Game
【思路要点】
- 按照题意模拟。
- 时间复杂度 O ( 1 ) O(1) O(1) 。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 2e5 + 5; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } string x, y; int main() { cin >> x; bool ans = false; for (int i = 1; i <= 5; i++) { cin >> y; ans |= y[0] == x[0] || y[1] == x[1]; } if (ans) puts("YES"); else puts("NO"); return 0; }
**【B】**Petr and a Combination Lock
【思路要点】
- 枚举每一次旋转的方向。
- 时间复杂度 O ( 2 N ) O(2^N) O(2N) 。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 2e5 + 5; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } int n, a[MAXN]; bool ans; void work(int pos, int sum) { if (pos == n + 1) ans |= sum % 360 == 0; else { work(pos + 1, sum + a[pos]); work(pos + 1, sum - a[pos]); } } int main() { read(n); for (int i = 1; i <= n; i++) read(a[i]); work(1, 0); if (ans) puts("YES"); else puts("NO"); return 0; }
**【C】**Yuhao and a Parenthesis
【思路要点】
- 将一个括号序列的 ( ( ( 看做 + 1 +1 +1 , ) ) ) 看做 − 1 -1 −1 ,我们可以用二元组 ( s u m , M i n ) (sum,Min) (sum,Min) 来描述一个括号序列,其中 s u m sum sum 表示序列和, M i n Min Min 表示前缀最小值。
- 若 s u m ≤ 0 sum≤0 sum≤0 ,要求 M i n = s u m Min=sum Min=sum ,否则,要求 M i n = 0 Min=0 Min=0 。
- 满足上述条件的 s u m sum sum 之和为 0 0 0 的括号序列可以配对,贪心即可。
- 时间复杂度 O ( ∑ ∣ s i ∣ ) O(\sum |s_i|) O(∑∣si∣) 。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 5e5 + 5; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } int cnt[MAXN], cmt[MAXN]; int main() { int n; read(n); for (int i = 1; i <= n; i++) { string x; cin >> x; int sum = 0, Min = 0; for (auto y : x) if (y == '(') sum++; else sum--, chkmin(Min, sum); if (sum <= 0) { if (Min == sum) cnt[-sum]++; } else { if (Min == 0) cmt[sum]++; } } int ans = cnt[0] / 2; for (int i = 1; i <= 5e5; i++) ans += min(cnt[i], cmt[i]); writeln(ans); return 0; }
**【D】**Makoto and a Blackboard
【思路要点】
- 可以发现,最后各个质因子 p p p 剩余指数为某一值 x x x 的概率在不同质因子之间是相互独立的。
- 将 N N N 质因数分解,对各个质因数分别 d p dp dp ,然后计算 N N N 各因数出现的概率即可。
- 时间复杂度 O ( K L o g 2 N + N ) O(KLog^2N+\sqrt{N}) O(KLog2N+N) 。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 1e5 + 5; const int P = 1e9 + 7; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } ll n; int k, num[MAXN]; int m, ans, val[MAXN], inv[MAXN]; int dp[MAXN][100], p[MAXN][100]; int power(int x, int y) { if (y == 0) return 1; int tmp = power(x, y / 2); if (y % 2 == 0) return 1ll * tmp * tmp % P; else return 1ll * tmp * tmp % P * x % P; } void update(int &x, int y) { x += y; if (x >= P) x -= P; } void getans(int pos, int sum, int pro) { if (pos == m + 1) { update(ans, 1ll * sum * pro % P); return; } for (int i = 0; i <= num[pos]; i++) { getans(pos + 1, sum, 1ll * pro * p[pos][i] % P); sum = 1ll * sum * val[pos] % P; } } int main() { read(n), read(k); for (ll i = 2; i * i <= n; i++) if (n % i == 0) { int cnt = 0; while (n % i == 0) n /= i, cnt++; num[++m] = cnt; val[m] = i; } if (n != 1) { num[++m] = 1; val[m] = n % P; } for (int i = 1; i <= 64; i++) inv[i] = power(i, P - 2); for (int i = 1; i <= m; i++) { for (int j = 0; j <= k; j++) for (int l = 0; l <= num[i]; l++) dp[j][l] = 0; dp[0][num[i]] = 1; for (int j = 1; j <= k; j++) for (int l = 0; l <= num[i]; l++) { for (int o = 0; o <= l; o++) update(dp[j][o], 1ll * dp[j - 1][l] * inv[l + 1] % P); } for (int j = 0; j <= num[i]; j++) p[i][j] = dp[k][j]; } getans(1, 1, 1); writeln(ans); return 0; }
**【E】**Egor and an RPG game
【思路要点】
- 考虑一类输入 { 1 , 3 , 2 , 6 , 5 , 4 , 10 , 9 , 8 , 7 , . . . } \{1,3,2,6,5,4,10,9,8,7,...\} {1,3,2,6,5,4,10,9,8,7,...} ,此类输入在长度 N N N 为 k ( k + 1 ) 2 \frac{k(k+1)}{2} 2k(k+1) 时可以构造出一组答案为 k k k 的输入,于是,一个猜想是 f ( x ) = m a x { k ∣ k ∈ Z , k ( k + 1 ) 2 ≤ x } f(x)=max\{k\ |\ k\in \Z,\frac{k(k+1)}{2}≤x\} f(x)=max{k ∣ k∈Z,2k(k+1)≤x} 。
- 我们提出一个构造算法来证明上述猜想。
- 形式化地来说,我们需要证明对于 N < k ( k + 1 ) 2 N<\frac{k(k+1)}{2} N<2k(k+1) ,存在一组 k − 1 k-1 k−1 的解。
- 求解输入序列的最长上升子序列,令其长度为 x x x 。
- 若 x ≥ k x≥k x≥k ,将该序列删除,递归解决该问题。
- 否则,根据 D i l w o r t h Dilworth Dilworth 定理,该序列可以被拆分为 x < k x<k x<k 个下降子序列,并且我们可以在求解最长上升子序列时顺带解决将其拆分为 x x x 个下降子序列的问题。
- 时间复杂度 O ( N N L o g N ) O(N\sqrt{N}LogN) O(NNLogN) 。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 1e5 + 5; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } int n, a[MAXN]; bool vis[MAXN]; vector <vector <int> > ans; void solve(int k) { static vector <int> low[MAXN]; int Max = 0; low[0].resize(1); static int dp[MAXN], from[MAXN]; for (int i = 1; i <= n; i++) { if (vis[a[i]]) continue; int l = 0, r = Max + 1; while (l < r) { int mid = (l + r) / 2; if (a[i] > low[mid].back()) l = mid + 1; else r = mid; } dp[a[i]] = l, from[a[i]] = low[l - 1].back(); if (l > Max) { Max = l; low[Max].clear(); low[Max].push_back(a[i]); } else low[l].push_back(a[i]); } if (Max == 0) return; if (Max >= k) { vector <int> res; res.clear(); int pos = 0; for (int i = 1; i <= n; i++) if (!vis[a[i]] && dp[a[i]] == Max) { pos = a[i]; break; } while (pos) { res.push_back(pos); vis[pos] = true; pos = from[pos]; } reverse(res.begin(), res.end()); ans.push_back(res); solve(k - 1); } else { for (int i = 1; i <= Max; i++) ans.push_back(low[i]); } } int main() { int T; read(T); while (T--) { read(n); for (int i = 1; i <= n; i++) { read(a[i]); vis[i] = false; } ans.clear(); int k = 1; while (k * (k + 1) / 2 <= n) k++; solve(k); writeln(ans.size()); for (auto x : ans) { write(x.size()); for (auto y : x) printf(" %d", y); putchar('\n'); } } return 0; }
**【F】**Alex and a TV Show
【思路要点】
- 如果我们用 b i t s e t bitset bitset 维护集合中的元素,那么操作 1 , 2 , 4 1,2,4 1,2,4 均可以轻松地实现。
- 考虑操作 3 3 3 ,通过莫比乌斯反演,结果集合中存在数字 x x x 当且仅当 ∑ i = 1 ⌊ v x ⌋ μ ( i ) ∗ s u m A ( i x ) ∗ s u m B ( i x ) = 1 ( m o d 2 ) \sum_{i=1}^{\lfloor\frac{v}{x}\rfloor}\mu(i)*sum_A(ix)*sum_B(ix)=1\ (mod\ 2) ∑i=1⌊xv⌋μ(i)∗sumA(ix)∗sumB(ix)=1 (mod 2) ,其中 s u m A ( x ) , s u m B ( x ) sum_A(x),sum_B(x) sumA(x),sumB(x) 分别表示 A , B A,B A,B 中 x x x 的倍数的个数。
- 那么,如果我们用 b i t s e t bitset bitset 维护集合中的 s u m sum sum ,那么操作 2 , 3 2,3 2,3 分别都可以用异或、按位与来实现,操作 4 4 4 的询问可以通过预处理莫比乌斯函数对应的 b i t s e t bitset bitset 来实现。
- 时间复杂度 O ( q v w ) O(\frac{qv}{w}) O(wqv) 。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 1e5 + 5; const int MAXM = 7e3 + 5; typedef long long ll; typedef bitset <MAXM> info; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } info a[MAXN], b[MAXM], getans[MAXM]; int tot, prime[MAXM], f[MAXM], miu[MAXM]; void init(int n) { miu[1] = 1; for (int i = 2; i <= n; i++) { if (f[i] == 0) { prime[++tot] = f[i] = i; miu[i] = -1; } for (int j = 1; j <= tot && prime[j] <= f[i]; j++) { int tmp = prime[j] * i; if (tmp > n) break; f[tmp] = prime[j]; if (prime[j] == f[i]) miu[tmp] = 0; else miu[tmp] = -miu[i]; } } for (int i = 1; i <= n; i++) { for (int j = i; j <= n; j += i) { b[j][i] = 1; if (miu[j / i]) getans[i][j] = 1; } } } int main() { init(7000); int n, m; read(n), read(m); for (int i = 1; i <= m; i++) { int opt, x, y, z; read(opt), read(x), read(y); if (opt == 1) a[x] = b[y]; if (opt == 2) { read(z); a[x] = a[y] ^ a[z]; } if (opt == 3) { read(z); a[x] = a[y] & a[z]; } if (opt == 4) putchar('0' + (a[x] & getans[y]).count() % 2); } return 0; }
**【G】**Vladislav and a Great Legend
【思路要点】
- 将 x k x^k xk 写成 ∑ i = 0 k S ( k , i ) ∗ i ! ∗ ( x i ) \sum_{i=0}^{k}S(k,i)*i!*\binom{x}{i} ∑i=0kS(k,i)∗i!∗(ix) 的形式。
- 因此,我们需要计算对于每一个点集对应的生成树,从中选出 i ( i = 0 , 1 , 2 , . . . , k ) i\ (i=0,1,2,...,k) i (i=0,1,2,...,k) 条边标记的总方案数。
- 考虑在每一棵生成树深度最低的点处计算其贡献,用树形背包解决即可。
- 时间复杂度 O ( N k ) O(Nk) O(Nk) 。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 1e5 + 5; const int MAXK = 205; const int P = 1e9 + 7; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } int n, k, fac[MAXK], s[MAXK][MAXK]; int dp[MAXN][MAXK], res[MAXK], size[MAXN]; vector <int> a[MAXN]; void update(int &x, int y) { x += y; if (x >= P) x -= P; } void times(int *a, int &la, int *b, int lb) { static int tmp[MAXK]; memset(tmp, 0, sizeof(tmp)); for (int i = 0; i <= la && i <= k; i++) for (int j = 0; j <= lb && i + j <= k; j++) update(tmp[i + j], 1ll * a[i] * b[j] % P); memcpy(a, tmp, sizeof(tmp)); la += lb; } void work(int pos, int fa) { size[pos] = 1, dp[pos][0] = 2; for (auto x : a[pos]) if (x != fa) { work(x, pos); times(dp[pos], size[pos], dp[x], size[x]); for (int i = 0; i <= k; i++) update(res[i], P - dp[x][i]); } for (int i = 0; i <= k; i++) update(res[i], dp[pos][i]); for (int i = k; i >= 1; i--) update(dp[pos][i], dp[pos][i - 1]); update(dp[pos][1], P - 1); } void init(int n, int k) { s[0][0] = fac[0] = 1; for (int i = 1; i <= k; i++) { fac[i] = 1ll * fac[i - 1] * i % P; for (int j = 1; j <= i; j++) s[i][j] = (s[i - 1][j - 1] + 1ll * s[i - 1][j] * j) % P; } } int main() { read(n), read(k); for (int i = 1; i <= n - 1; i++) { int x, y; read(x), read(y); a[x].push_back(y); a[y].push_back(x); } init(n, k); work(1, 0); int ans = 0; for (int i = 0; i <= k; i++) update(ans, 1ll * s[k][i] * fac[i] % P * res[i] % P); writeln(ans); return 0; }
**【H】**Mateusz and an Infinite Sequence
【思路要点】
- 考虑如何描述一个区间里的元素。
- 我们需要记录其长度 ( l e n ) (len) (len) ,包含可行子串的个数 ( a n s ) (ans) (ans) ,其长度为 i i i 的后缀是否能作为可行子串长度为 i i i 的前缀(没有出现的元素当做 0 0 0 ) ( s u f i ( i = 1 , 2 , 3 , . . . , N − 1 ) ) (suf_i\ (i=1,2,3,...,N-1)) (sufi (i=1,2,3,...,N−1)) ,长度为 N − i N-i N−i 的前缀是否能作为可行子串长度为 N − i N-i N−i 的后缀(没有出现的元素当做 0 0 0 ) ( p r e i ( i = 1 , 2 , 3 , . . . , N − 1 ) ) (pre_i\ (i=1,2,3,...,N-1)) (prei (i=1,2,3,...,N−1)) 。
- 上述信息是可以合并的,并可以利用 b i t s e t bitset bitset 做到 O ( N w ) O(\frac{N}{w}) O(wN) 合并。
- 接下来的部分就是一个简单数位 d p dp dp ,记 d p i , j dp_{i,j} dpi,j 表示序列的前 d i d^i di 位加上 j j j 后的信息,可以简单计算 d p dp dp 值,并求解答案。
- 时间复杂度 O ( N d M L o g M w ) O(\frac{NdMLogM}{w}) O(wNdMLogM) 。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 3e4 + 5; const int MAXD = 25; const int MAXM = 65; const int MAXLOG = 64; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } struct info { ll ans, len; bitset <MAXN> pre, suf; }; bitset <MAXN> all; int n, d, m, gen[MAXD], b[MAXN]; int tot; ll len[MAXLOG]; info dp[MAXLOG][MAXM]; info operator + (const info &a, const info &b) { if (a.len == 0) return b; info res; res.len = a.len + b.len; res.ans = a.ans + b.ans; res.pre = a.pre; res.suf = b.suf; if (a.len <= n - 2) res.pre &= (b.pre >> a.len) | (all << (n - 1 - a.len)); if (b.len <= n - 2) res.suf &= (a.suf << b.len) | (all >> (n - 1 - b.len)); if (a.len + b.len >= n) { bitset <MAXN> tmp = a.suf & b.pre; if (a.len <= n - 2) tmp &= all >> (n - 1 - a.len); if (b.len <= n - 2) tmp &= all << (n - 1 - b.len); res.ans += tmp.count(); } return res; } ll work(ll x) { info res; res.ans = 0; res.len = 0; res.pre.reset(); res.suf.reset(); int add = 0; for (int i = tot; i >= 0; i--) { for (int j = 1; j <= d; j++) if (x >= len[i]) { x -= len[i]; res = res + dp[i][(add + gen[j]) % m]; } else { add = (add + gen[j]) % m; break; } } return res.ans; } int main() { read(d), read(m); for (int i = 1; i <= d; i++) read(gen[i]); read(n); for (int i = 1; i <= n; i++) read(b[i]); for (int i = 1; i <= n - 1; i++) all.set(i); tot = 0, len[0] = 1; for (int i = 0; i <= m - 1; i++) { dp[0][i].len = 1; if (n == 1) dp[0][i].ans = i <= b[1]; else { for (int j = 1; j <= n - 1; j++) { dp[0][i].pre[j] = i <= b[j + 1]; dp[0][i].suf[j] = i <= b[j]; } } } ll ql, qr; read(ql), read(qr); while (len[tot] <= qr / d) { tot++, len[tot] = len[tot - 1] * d; for (int i = 0; i <= m - 1; i++) for (int j = 1; j <= d; j++) dp[tot][i] = dp[tot][i] + dp[tot - 1][(i + gen[j]) % m]; } writeln(work(qr) - work(ql + n - 2)); return 0; }