做题记录 3
Underground Lab
来源:CF780E, 2200
题目是保证有解的,所以对于任意的一棵树来说也一定有解,那就不妨思考树的情况.
这道题最苛刻的情况就是 $\mathrm{K=1}$, 即意味着需要遍历完整颗树且中间不能断开.
很自然想到构建欧拉序,不过长度是 $\mathrm{O(3n)}$ 的,那就魔改一下:每次便利完儿子后不再将 $\mathrm{x}$ 加入,这样就好了.
#include <bits/stdc++.h> #define N 400009 #define ll long long #define pb push_back #define setIO(s) freopen(s".in","r",stdin) using namespace std; struct UFS { int p[N]; void init() { for(int i = 0; i < N ; ++ i) p[i] = i; } int find(int x) { return p[x] == x ? x : p[x] = find(p[x]); } int merge(int x, int y) { x = find(x), y = find(y); if(x == y) return 0; p[x] = y; return 1; } }T; int n, m, K, bu[N], scc; vector<int>G[N]; void dfs(int x, int ff) { bu[++ scc] = x; for(int i = 0; i < G[x].size() ; ++ i) { int v = G[x][i]; if(v == ff) continue; dfs(v, x); bu[++ scc] = x; } } int main() { /// setIO("input"); scanf("%d%d%d", &n, &m, &K); T.init(); for(int i = 1; i <= m ; ++ i) { int x, y; scanf("%d%d", &x, &y); if(T.merge(x, y)) { G[x].pb(y); G[y].pb(x); } } dfs(1, 0); int num = (2 * n + K - 1) / K; int cur = 1; for(int i = 1; i <= K ; ++ i) { if(cur > scc) { printf("%d %d\n", 1, 1); } else { int l = cur, r = min(scc, cur + num - 1); printf("%d ", r - l + 1); for(int j = l; j <= r; ++ j) { printf("%d ", bu[j]); } printf("\n"); cur = r + 1; } } return 0; }
Send Boxes to Alice (Hard Version)
来源:CF1254B2, 2100
$K$ 一定是综合的因数,所以不妨考虑枚举这个 $\mathrm{K}$.
然后显然 $\mathrm{K}$ 是质因数的情况是最好的,因为会更稠密,更容易满足.
对于一个 $\mathrm{K}$, 令所有数字对 $\mathrm{K}$ 取模,表示需要补齐的部分.
假设前 $\mathrm{i-1}$ 个数字都补好,就差 $\mathrm{i}$, 那么 $\mathrm{i}$ 差的就是前 $\mathrm{i}$ 个数的和模 $\mathrm{K}$.
这个用 $\mathrm{i+1}$ 来贡献的话就是 $\mathrm{i}$ 移向 $\mathrm{i+1}$ 或 $\mathrm{i+1}$ 移向 $i$, 但 $\mathrm{i+1}$ 不会变.
#include <cstdio> #include <vector> #include <cstring> #include <algorithm> #define N 1000009 #define ll long long #define pb push_back #define setIO(s) freopen(s".in","r",stdin) using namespace std; ll b[N], a[N], sum, ans; int n ; vector<ll>g; void calc(ll K) { ll det = 0, fin = 0; for(int i = 1; i <= n ; ++ i) { b[i] = a[i] % K; (det += b[i]) %= K; // printf("%lld %lld\n", b[i], det); fin += min(det, K - det); } ans = min(ans, fin); } int main() { // setIO("input"); scanf("%d",&n); for(int i = 1; i <= n ; ++ i) { scanf("%lld", &a[i]); sum += a[i]; } ans = 1005193768297041258ll; for(ll i = 2; i * i <= sum; ++ i) { if(sum % i == 0) { g.pb(i); while(sum % i == 0) sum /= i; } } if(sum > 1) g.pb(sum); for(int i = 0; i < g.size() ; ++ i) { calc(g[i]); } printf("%lld", ans == 1005193768297041258ll ? -1 : ans); return 0; }
Bitwise Queries (Hard Version)
来源:CF1451E2, 2300
交互题.
显然可以用 $\mathrm{n-1}$ 次操作来求出每个数与第一个数字的异或.
然后这道题有一个性质非常有用,就是数字范围在 $\mathrm{[0, n-1]}$ 之间.
利用这个性质,发现要么所有数字都连续,要么会出现相等的情况.
然后相等的情况就把等的找出来然后判断异或对应的 0 和 1 对应真实的值是什么.
如果出现连续的情况就找到连续的 $\mathrm{x,x+1}$ 然后可以判断除了 0 位以外的所有.
判断 0 位就让其中 0 位为 0 的和 1 去与一下,看看是什么.
第一种操作为 $\mathrm{n}$, 第二种为 $\mathrm{n+1}$, 故符合答案要求.
#include <cstdio> #include <vector> #include <cstring> #include <algorithm> #define N 500009 #define pb push_back #define ll long long #define setIO(s) freopen(s".in","r",stdin) using namespace std; vector<int>g[N], h[N]; int val[N]; int n , a[N], Lg[N], mx, vis[N], pos[N]; int AND(int x, int y) { printf("AND %d %d\n", x, y); printf("\n"); fflush(stdout); int p; scanf("%d", &p); return p; } int XOR(int x, int y) { printf("XOR %d %d\n", x, y); printf("\n"); fflush(stdout); int p; scanf("%d", &p); return p; } void init() { // Lg[1] = 0; for(int i = 0; i <= 17; ++ i) { Lg[1 << i] = i; } } // val[j]: 第 j 位为 1 时对应的结果. void s1() { // 这个是存在相同的情况. for(int i = 0; i < N ; ++ i) { if(vis[i] > 1) { // 个数要大于 1. int x = g[i][0], y = g[i][1]; int c = AND(x, y); for(int j = 0; j <= mx; ++ j) { int v = 0; if(c & (1 << j)) v = 1; val[j] = ((a[x] >> j) & 1) ? v : (1 ^ v); } break; } } } void s2() { for(int i = 1; i <= n ; ++ i) { int p = (a[i] & 1) ? a[i] - 1: a[i]; h[p].pb(i); } for(int i = 0; i < N ; ++ i) { if(h[i].size() > 1) { // 有两个相邻的. int x = h[i][0], y = h[i][1]; if(x == 1 || y == 1) continue; int c = AND(x, y); for(int j = 1; j <= mx ; ++ j) { // 2 ^ i 位的情况. int v = 0; if(c & (1 << j)) v = 1; val[j] = ((a[x] >> j) & 1) ? v : (1 ^ v); } // 看看 val[0] 行不行. if(a[h[i][0]] & 1) { // a[h[i][1]] 为 0. int det = (AND(1, h[i][1]) & 1); val[0] = (det ^ 1); } else { int det = (AND(1, h[i][0]) & 1); val[0] = (det ^ 1); } break; } } } int main() { // setIO("input"); scanf("%d",&n); init(); ++ vis[0]; g[0].pb(1); for(int i = 2; i <= n ; ++ i) { a[i] = XOR(1, i); vis[a[i]] ++ ; g[a[i]].pb(i); } int fl = 0; for(int i = 0; i < N ; ++ i) { if(vis[i] > 1) { fl = 1; break; } } mx = Lg[n] - 1; if(fl) { s1(); } else { s2(); } printf("! "); for(int i = 1; i <= n ; ++ i) { int o = 0; for(int j = 0; j <= mx ; ++ j) { int cur = 0; if(a[i] & (1 << j)) { cur = 1; } if(cur) o += val[j] << j; else o += (val[j] ^ 1) << j; } printf("%d ", o); } fflush(stdout); return 0; }
The LCMs Must be Large
来源:CF1166E, 2100
看到题面后就知道这道题肯定有什么结论了.
然后这道题的结论就是若所给的 $\mathrm{m}$ 个集合存在两个集合无交集就不合法.
这个是显而易见的,然后若不出现上面的情况就一定合法,证明过程还没研究.
#include <cstdio> #include <bitset> #include <cstring> #include <algorithm> #define N 10009 #define ll long long #define pb push_back #define setIO(s) freopen(s".in","r",stdin) using namespace std; int m, n; bitset<N>bi[51]; int main() { // setIO("input"); scanf("%d%d", &m, &n); for(int i = 1; i <= m ; ++ i) { int s, x; scanf("%d", &s); for(int j = 1; j <= s; ++ j) { scanf("%d", &x); bi[i].set(x); } for(int j = 1; j < i ; ++ j) { if(!(bi[i] & bi[j]).count()) { printf("impossible"); return 0; } } } printf("possible"); return 0; }
Tree Constructing
来源:CF1003E, 2100
这道题的构造思路就是把最长链拿出来,然后其它点往上挂.
可以预处理每个点能挂的最大深度,能挂的最大点数,然后 $\mathrm{dfs}$ 输出即可.
#include <cstdio> #include <vector> #include <cstring> #include <algorithm> #define N 400009 #define ll long long #define pb push_back #define setIO(s) freopen(s".in","r",stdin) using namespace std; int n, d, K, cnt; ll f[N]; void dfs(int cur, int ff, int de, int mx) { if(cur <= n) printf("%d %d\n", cur, ff); if(cnt == n) return ; if(de == mx) return ; for(int i = 1; i < K ; ++ i) { if(cnt >= n) exit(0); dfs(++ cnt, cur, de + 1, mx); } } int main() { // setIO("input"); scanf("%d%d%d", &n, &d, &K); f[1] = 1, ++ d; if(d > n) { printf("NO"); return 0; } if(K == 1) { if(n == 1) { if(d == 0) printf("YES\n"); else printf("NO\n"); } else if(n == 2) { if(d == 2) { printf("YES\n"); printf("%d %d\n", 1, 2); } else printf("NO\n"); } else { printf("NO\n"); } return 0; } ll prev = 1; for(int i = 2; i < N ; ++ i) { f[i] = f[i - 1] + 1ll * prev * (K - 1); if(f[i] > 1ll * n) { for(int j = i; j < N ; ++ j) f[j] = n + 1; break; } prev = 1ll * prev * (K - 1); } int rem = n - d, lst = 0; for(int i = 2; i < d; ++ i) { int dep = min(i - 1, d - i); if(1ll * f[dep] * (K - 2) >= 1ll * rem) { lst = i, rem = 0; break; } else { rem -= f[dep] * (K - 2); } } if(rem) { printf("NO"); } else { printf("YES\n"); for(int i = 2; i <= d; ++ i) { printf("%d %d\n", i - 1, i); } cnt = d; for(int i = 2; i < lst; ++ i) { int dep = min(i - 1, d - i); // 这里都是满的. for(int j = 1; j <= K - 2; ++ j) { dfs(++ cnt, i, 1, dep); } } rem = n - cnt; // 最后构造 lst 即可. for(int j = 1; j <= K - 2; ++ j) { int dep = min(lst - 1, d - lst); if(rem == 0) break; if(f[dep] <= rem) { dfs(++ cnt, lst, 1, dep); rem -= f[dep]; } else { // f[dep] > rem. dfs(++ cnt, lst, 1, dep); } } } return 0; }
Minimax
来源:CF1530E, 2100
构造题.
对于要求答案最小化的构造题,先考虑答案可能的取值情况.
1. 所有字符相等,直接输出
2. 存在一个字符使得该字符出现一次,答案为 0.
3. 其余情况答案为 1.
前 2 种情况好处理,主要看第三种.
第三种情况的话枚举一下开头,即 aa 或 ab.
#include <cstdio> #include <vector> #include <cstring> #include <set> #include <map> #include <algorithm> #define N 100009 #define ll long long #define pb push_back #define setIO(s) freopen(s".in","r",stdin) using namespace std; char str[N]; int n, c[30], num[N]; vector<int>g; void print() { for(int i = 0; i < 26; ++ i) { for(int j = 1; j <= c[i]; ++ j) printf("%c", i + 'a'); } printf("\n"); } int check(int x, int y) { // 前两个字符是一样的. if(c[g[x]] < 2) { return 0; } int rem = c[g[x]] - 2; if(n - c[g[x]] >= rem) return 1; else return 0; } void sol(int x, int y) { if(x == y) { // 全为第一个. int cnt = 0; for(int i = 1; i < g.size() ; ++ i) { for(int j = 1; j <= c[g[i]]; ++ j) num[++ cnt] = g[i]; } printf("%c%c", g[0] + 'a', g[0] + 'a'); c[g[0]] -= 2; int d = 1; for(int i = 3; i <= n ; ++ i) { if((i & 1)) { printf("%c", 'a' + num[d]), ++ d; } else { if(c[g[0]]) printf("%c", 'a' + g[0]), -- c[g[0]]; else printf("%c", 'a' + num[d]) , ++ d; } } printf("\n"); } else { // x != y, abbbbbbbbbbbbbaaaaa // abaaaaaaaaaaaaaaaaaaaaacbbbbbbbbbbb if(g.size() == 2) { printf("%c%c", g[0] + 'a', g[1] + 'a'); c[g[0]] -= 1, c[g[1]] -= 1; for(int i = 1; i <= c[g[1]]; ++ i) printf("%c", 'a' + g[1]); for(int i = 1; i <= c[g[0]]; ++ i) printf("%c", 'a' + g[0]); } else { printf("%c%c", g[0] + 'a', g[1] + 'a'); c[g[0]] -= 1, c[g[1]] -= 1; for(int i = 1; i <= c[g[0]]; ++ i) printf("%c", 'a' + g[0]); printf("%c", g[2]+ 'a'), --c[g[2]]; for(int i = 1; i < g.size() ; ++ i) { for(int j = 1; j <= c[g[i]]; ++ j) printf("%c", 'a' + g[i]); } } printf("\n"); } } void cal2() { // in this case, length of border = 1. if(check(0, 0)) sol(0, 0); else sol(0, 1); } // border = 1 的情况. void pt() { int mn = 0; for(int i = 0; i < 26; ++ i) if(c[i] == 1) { mn = i; break; } printf("%c", mn + 'a'); for(int i = 0; i < 26; ++ i) if(i != mn) { for(int j = 1; j <= c[i] ; ++ j) printf("%c", i + 'a'); } printf("\n"); } void solve() { scanf("%s", str + 1); n = strlen(str + 1); for(int i = 1; i <= n; ++ i) { int cur = str[i] - 'a'; if(!c[cur]) g.pb(cur); ++c[cur]; } sort(g.begin(), g.end()); if(g.size() == 1 || g.size() == n) print(); else { // 还有 border = 0 的情况: int mn = -1; for(int i = 0; i < 26; ++ i) if(c[i] == 1) {mn = i; break; } if(mn != -1) pt(); else cal2(); } memset(c, 0, sizeof(c)), g.clear(); } int main() { // setIO("input"); int T; scanf("%d", &T); while(T--) solve(); return 0; }
Permutation Shift
来源:CF1553E, 2100
给定一个排列,可以随意交换两个数,问最少多少次变成 1 到 n.
这个问题可以将数字和位置连边,然后判断有几个环,答案就是 n - 环数.
这道题直观上肯定先去进行枚举位移,然后判断是否合法.
然后题中交换次数给了一个奇怪的限定,退一下式子发现答案要满足正确位置大于等于 n/3.
而又有所有正确位置之和为 n, 故最多只有 3 种 k 值.
最后暴力枚举,然后连边跑 dfs 判断即可.
#include <cstdio> #include <cstring> #include <vector> #include <algorithm> #define N 300009 #define pb push_back #define setIO(s) freopen(s".in","r",stdin) using namespace std; vector<int>G[N], an; int n, m, p[N], vis[N], cnt[N], a[N]; void dfs(int x) { vis[x] = 1; for(int i = 0; i < G[x].size() ; ++ i) { if(!vis[G[x][i]]) dfs(G[x][i]); } } void solve() { scanf("%d%d", &n, &m); for(int i = 0; i <= n ; ++ i) cnt[i] = 0; for(int i = 1; i <= n ; ++ i) { scanf("%d", &p[i]); if(p[i] >= i) cnt[p[i] - i] ++ ; else { cnt[n - i + p[i]] ++ ; } } an.clear(); for(int i = 0; i < n ; ++ i) { if(cnt[i] < n - 2 * m) continue; // printf("%d %d\n", i, cnt[i]); for(int j = 1; j <= n ; ++ j) { int nex = ((j - 1) + i) % n + 1; a[nex] = p[j]; } for(int j = 1; j <= n ; ++ j) { if(a[j] != j) G[j].pb(a[j]); } int c = 0; for(int j = 1; j <= n ; ++ j) { if(!vis[j]) ++ c, dfs(j); } // printf("%d\n", c); for(int j = 1; j <= n ; ++ j) G[j].clear(), vis[j] = 0; if(n - c <= m) an.pb((n - i) % n); } printf("%d ", an.size()); sort(an.begin(), an.end()); for(int i = 0; i < an.size() ; ++ i) printf("%d ", an[i]); printf("\n"); } int main() { // setIO("input"); int T; scanf("%d", &T); while(T-- ) solve(); return 0; }
Errich-Tac-Toe (Hard Version)
来源:CF1450C2, 2300
构造题.
在简单版本中,可以直接按 3 的余数进行棋盘染色,然后每次只改变一类点.
在本题中,可以强制令两类点都改成 $X$ 与 $O$.
利用抽屉原理,有 3 种染色方案且和为 $\mathrm{K}$, 所以最小的方案肯定小于 K/3.
#include <cstdio> #include <cstring> #include <algorithm> #define N 305 #define ll long long #define pb push_back #define setIO(s) freopen(s".in","r",stdin) using namespace std; int n ; char str[N][N]; void print(int x, int y, int z) { for(int i = 1; i <= n ; ++ i) { for(int j = 1; j <= n ; ++ j) { if(str[i][j] == '.') { printf("%c", str[i][j]); continue; } int d = (i + j) % 3; if(d == 0) { if(x == -1) printf("%c", str[i][j]); else if(x == 0) printf("O"); else printf("X"); } if(d == 1) { if(y == -1) printf("%c", str[i][j]); else if(y == 0) printf("O"); else printf("X"); } if(d == 2) { if(z == -1) printf("%c", str[i][j]); else if(z == 0) printf("O"); else printf("X"); } } printf("\n"); } } void solve() { scanf("%d", &n); for(int i = 1; i <= n ; ++ i) { scanf("%s", str[i] + 1); } int c[4][2], tot = 0; memset(c, 0, sizeof(c)); for(int i = 1; i <= n ; ++ i) { for(int j = 1; j <= n ; ++ j) { int d = (i + j) % 3; if(str[i][j] == '.') continue; ++ tot; if(str[i][j] == 'X') c[d][0] ++ ; else c[d][1] ++ ; } } if(c[0][0] + c[1][1] <= tot / 3) print(0, 1, -1); else if(c[1][0] + c[2][1] <= tot / 3) print(-1, 0, 1); else print(1, -1, 0); } int main() { // setIO("input"); int T; scanf("%d", &T); while(T--) solve(); return 0; }
Trains and Statistic
来源:CF675E, 2300
贪心题.
令 $\mathrm{sum[i]}$ 表示以 $\mathrm{i}$ 为起点的答案和.
那么显然只需跳一步的就是 $\mathrm{[i, a[i]]}$ 内的.
然后如果是在 $\mathrm{a[i]}$ 之外,肯定要找区间内跳的最远的.
然后直接转移一下即可.
#include <cstdio> #include <cstring> #include <algorithm> #define N 200009 #define ll long long #define setIO(s) freopen(s".in","r",stdin) using namespace std; int a[N], n, Lg[N], mi[N][20]; ll sum[N]; void init() { Lg[1] = 0; for(int i = 2; i < N ; ++ i) Lg[i] = Lg[i >> 1] + 1; for(int i = 1; i <= n ; ++ i) mi[i][0] = i; for(int i = 1; i < 19 ; ++ i) { for(int j = 1; j + (1 << i) - 1 <= n ; ++ j) { if(a[mi[j][i - 1]] > a[mi[j + (1 << (i - 1))][i - 1]]) mi[j][i] = mi[j][i - 1]; else mi[j][i] = mi[j + (1 << (i - 1))][i - 1]; } } } int query(int l, int r) { int p = Lg[r - l + 1]; if(a[mi[l][p]] > a[mi[r - (1 << p) + 1][p]]) return mi[l][p]; else return mi[r - (1 << p) + 1][p]; } int main() { // setIO("input"); scanf("%d",&n); for(int i = 1; i < n ; ++ i) { scanf("%d", &a[i]); } init(); sum[n] = 0; for(int i = n - 1; i >= 1; -- i) { int l = i + 1, r = a[i]; if(a[i] == n) { sum[i] = n - i; } else { int id = query(l, r); sum[i] = sum[id] + id + n - i - a[i]; } // printf("%d %lld\n", i, sum[i]); } ll ans = 0; for(int i = 1; i <= n ; ++ i) ans += sum[i]; printf("%lld", ans); return 0; }
Magic Stones
来源:CF1110E, 2200
需要手玩性质.
显然,合法的必要条件是首末两个数字必须对应相等.
然后我们需要寻求变换过程中的不变量,看看两个序列是否有给定量可以判定相等.
然后发现变换之后差分数组等于说是左右交换,所以只需判断一下两个序列差分数组是否排完序后相等即可.
#include <cstdio> #include <cstring> #include <vector> #include <algorithm> #define N 1000009 #define ll long long #define setIO(s) freopen(s".in","r",stdin) using namespace std; int n, c[N], t[N]; void solve() { scanf("%d", &n); for(int i = 1; i <= n ; ++ i) { scanf("%d", &c[i]); } for(int i = 1; i <= n ; ++ i) { scanf("%d", &t[i]); } if(c[1] != t[1] || c[n] != t[n]) { printf("No\n"); } else { for(int i = n; i >= 2; -- i) { c[i] -= c[i - 1]; t[i] -= t[i - 1]; } sort(c + 2, c + 1 + n); sort(t + 2, t + 1 + n); for(int i = 2; i <= n ; ++ i) { if(c[i] != t[i]) { printf("No\n"); return ; } } printf("Yes\n"); } } int main() { // setIO("input"); int T = 1; // scanf("%d", &T); while(T -- ) solve(); return 0; }
Placing Rooks
来源:CF1342E, 2300
可以将问题转化为:在区间 $\mathrm{[1, n-k]}$ 中选择 $\mathrm{n}$ 个数且 $1$ 至 $\mathrm{n-k}$ 都被选到.
然后利用容斥原理,枚举交集为 $\mathrm{i}$ 个数空着的情况,最后加和一下即可.
注意特判无解和 $\mathrm{k=0}$ 的情况.
#include <cstdio> #include <cstring> #include <algorithm> #define N 200009 #define ll long long #define mod 998244353 #define setIO(s) freopen(s".in","r",stdin) using namespace std; int n, k; ll K; int fac[N], inv[N]; int qpow(int x, int y) { int tmp = 1; for(; y; y >>= 1, x = (ll)x * x % mod) if(y & 1) tmp = (ll)tmp * x % mod; return tmp; } int get_inv(int x) { return qpow(x, mod - 2); } void init() { fac[0] = inv[0] = 1; for(int i = 1; i < N ; ++ i) { fac[i] = (ll)fac[i - 1] * i % mod; } inv[N - 1] = get_inv(fac[N - 1]); for(int i = N - 2; i >= 1; -- i) { inv[i] = (ll)inv[i + 1] * (i + 1) % mod; } } int C(int x, int y) { return (ll)fac[x] * inv[y] % mod * inv[x - y] % mod; } // h 个空位. int F(int h) { return (ll)C(n - k, h) * qpow(n - k - h, n) % mod; } void solve() { k = K; int ans = 0; for(int i = 0; i <= n - k; ++ i) { int d = (i & 1) ? (mod - 1) : 1; (ans += (ll)C(n, k) * d % mod * F(i) % mod) %= mod; } // printf("%d\n", ans); printf("%d", (ll)((ll)ans * 2 % mod) % mod); } int main() { // setIO("input"); init(); scanf("%d%lld", &n, &K); if(K == 0) { printf("%d", fac[n]); } else if(K > n) printf("0"); else { solve(); } return 0; }
MEX Sequences
来源:CF1073E
构造一下,发现合法的情况有 3 种:
1. 全为 1
2. 一个逐次 +1 的序列.
3. 一个前半部分逐次加一,后半部分相差为 2 的波动序列.
针对 3 种情况 $\mathrm{DP}$ 弄一下即可.
#include <cstdio> #include <cstring> #include <algorithm> #define N 500009 #define ll long long #define mod 998244353 #define setIO(s) freopen(s".in","r",stdin) using namespace std; int qpow(int x, int y) { int tmp = 1; for(; y; y >>= 1, x = (ll)x * x % mod) { if(y & 1) tmp = (ll)tmp * x % mod; } return tmp; } int a[N], pw[N], f[N], cnt[N], g[N][2], suf[N][2], n ; void init() { pw[0] = 1; for(int i = 1; i < N ; ++ i) { pw[i] = (ll)pw[i - 1] * 2 % mod; } } void solve() { scanf("%d", &n); int c1 = 0; for(int i = 1; i <= n ; ++ i) { scanf("%d", &a[i]); if(a[i] == 1) ++ c1 ; } // g[i][0]: i and i - 2 // g[i][1]: i and i + 2 for(int i = n ; i >= 1; -- i) { // calc g[a[i]][0]; int c0 = (ll)(suf[a[i]][0] + 1) % mod; if(a[i] - 2 >= 0) (c0 += suf[a[i] - 2][1]) %= mod; int c1 = (ll)(suf[a[i]][1] + 1) % mod; if(a[i] + 2 <= n) (c1 += suf[a[i] + 2][0]) %= mod; g[i][0] = c0, g[i][1] = c1; (suf[a[i]][0] += c0) %= mod; (suf[a[i]][1] += c1) %= mod; } int ans = 0; for(int i = 1; i <= n ; ++ i) { if(a[i] == 0) { ++ cnt[0]; f[0] = (ll)(pw[cnt[0]] - 1 + mod) % mod; } else { ++ cnt[a[i]]; f[a[i]] = (ll)f[a[i]] * 2 % mod; (f[a[i]] += f[a[i] - 1]) %= mod; } if(a[i] - 2 >= 0) { (ans += (ll)f[a[i] - 2] * g[i][0] % mod) %= mod; } } (ans += (ll)(pw[c1] - 1 + mod) % mod) %= mod; for(int i = 0; i <= n ; ++ i) { (ans += f[i]) %= mod; } for(int i = 0; i <= n ; ++ i) { f[i] = g[i][0] = g[i][1] = cnt[i] = suf[i][0] = suf[i][1] = 0; } printf("%d\n", ans); } int main() { // setIO("input"); int T; init(); scanf("%d", &T); while(T--) solve(); return 0; }
Segment Sum
来源:CF1073E, 2300
数位 DP.
可以二进制将每种数字选不选压缩起来,然后跑两遍数位 DP.
第一遍记录方案数,第二遍统计答案.
这里要注意要把第一遍所维护的 $\mathrm{f}$ 数组所有情况都要枚举到,因为第二种情况要用.
#include <cstdio> #include <cstring> #include <algorithm> #define N 20 #define ll long long #define mod 998244353 #define setIO(s) freopen(s".in","r",stdin) using namespace std; int lowbit(int x) { return x & (-x); } int cnt[1 << 12], a[N], K, len, f[N][1 << 12][2][2], g[N][1 << 12][2][2], pw[N]; // DP 状态: f[cur][sta][is][fl]; void init() { cnt[0] = 0; for(int i = 1; i < (1 << 12) ; ++ i) cnt[i] = cnt[i - lowbit(i)] + 1; pw[0] = 1; for(int i = 1; i <= 18 ; ++ i) pw[i] = (ll)pw[i - 1] * 10 % mod; } int dfs(int cur, int sta, int is, int fl) { if(cur > len) { return f[cur][sta][is][fl] = (!is); } if(f[cur][sta][is][fl] != -1) return f[cur][sta][is][fl]; int d = fl ? a[cur] : 9; int an = 0; // printf("%d %d\n", cur, d); for(int i = 0; i <= d; ++ i) { if(is) { // 前面全是 0. if(i == 0) { an += dfs(cur + 1, sta, is, fl && (i == d)); if(an >= mod) an -= mod; } else { if(cnt[sta | (1 << i)] > K) continue; an += dfs(cur + 1, sta | (1 << i), 0, fl && (i == d)); if(an >= mod) an -= mod; } } else { if(cnt[sta | (1 << i)] > K) continue; an += dfs(cur + 1, sta | (1 << i), 0, fl && (i == d)); if(an >= mod) an -= mod; } } return f[cur][sta][is][fl] = an; } int cal(int cur, int sta, int is, int fl) { //printf("%d\n", sta); if(cur > len) { return 0; } if(g[cur][sta][is][fl] != -1) return g[cur][sta][is][fl]; int d = fl ? a[cur] : 9; int an = 0; g[cur][sta][is][fl] = 0; for(int i = 0; i <= d; ++ i) { if(is) { // 前面全是 0. if(i == 0) { an += cal(cur + 1, sta, is, fl && (i == d)); if(an >= mod) an -= mod; } else { if(cnt[sta | (1 << i)] > K) continue; an += (ll)(cal(cur + 1, sta | (1 << i), 0, fl && (i == d)) + (ll)i * pw[len - cur] % mod * f[cur + 1][sta | (1 << i)][0][fl && (i == d)] % mod) % mod; an %= mod; } } else { if(cnt[sta | (1 << i)] > K) continue; an += (ll)((ll)pw[len - cur] * i % mod * f[cur + 1][sta | (1 << i)][0][fl && (i == d)] % mod + cal(cur + 1, sta | (1 << i), 0, fl && (i == d))) % mod; an %= mod; } } return g[cur][sta][is][fl] = an; } int solve(ll X) { memset(f, -1, sizeof(f)); memset(g, -1, sizeof(g)); len = 0; while(X) { a[ ++ len ] = X % 10; X /= 10; } for(int i = 1; i <= len / 2; ++ i) swap(a[i], a[len - i + 1]); dfs(1, 0, 1, 1); return cal(1, 0, 1, 1); } int main() { // setIO("input"); init(); ll L, R; scanf("%lld%lld%d", &L, &R, &K); printf("%d\n", (ll)(solve(R) - solve(L - 1) + mod) % mod); return 0; }
Mike and Frog
来源:CF547A, 2200
这道题没有思维难度,但是有很多情况需要特殊讨论一下.
一个数字的步数表述成 $\mathrm{y=kx+b}$ 的形式.
$\mathrm{b}$ 是从初始位置走到该数字的步数,$\mathrm{k}$ 是模意义下的循环节.
这里要分几种情况讨论:
1. 两个数都具有 $\mathrm{k,b}$.
2. 两个数都没有 $\mathrm{k}$.
3. 其中一个数字有 $\mathrm{k}$.
第一种情况用 $\mathrm{exgcd}$ 求解,后面算一下即可.
#include <cstdio> #include <vector> #include <cstring> #include <algorithm> #define pb push_back #define ll long long #define N 1000009 #define setIO(s) freopen(s".in","r",stdin) using namespace std; struct Math { int exgcd(ll a, ll b, ll &x, ll &y) { if(!b) { x = 1, y = 0; return a; } int gc = exgcd(b, a % b, x, y); ll t = x; x = y, y = t - (a / b) * y; return gc; } ll solve(int a, int b, int c) { ll x, y; int c0 = exgcd(a, b, x, y); if(c % c0) return -1; ll det = abs(b / c0); ll an = (x * (c / c0) % det + det) % det; ll y0 = (c - an * a) / b; if(y0 >= 0) return an; else { // y0 < 0 ll d = abs(a / c0); ll t = (abs(y0) + d - 1) / d; return an + 1ll * t * det; } } }T; int m; int h[2], x[2], y[2], a[2], vis[N]; struct node { int k, b; node(int k = 0, int b = 0):k(k), b(b){} }A[2]; node solve(int id) { memset(vis, -1, sizeof(vis)); int cur = h[id]; vis[cur] = 0; while(1) { int nex = (ll)(1ll * x[id] * cur % m + y[id]) % m; if(vis[nex] != -1) break; vis[nex] = vis[cur] + 1, cur = nex; } if(vis[a[id]] == -1) { return node(-1, -1); } else { int b = vis[a[id]], k = 0; // b 为步数. cur = a[id]; memset(vis, -1, sizeof(vis)); while(1) { ++ k; int nex = (ll)((ll)cur * x[id] % m + y[id]) % m; if(nex == a[id]) { return node(k, b); } if(vis[nex] != -1) break; vis[nex] = 1, cur = nex; } return node(-233, b); } } int main() { // setIO("input"); scanf("%d",&m); for(int i = 0; i < 2; ++ i) { scanf("%d%d%d%d",&h[i],&a[i],&x[i],&y[i]); A[i] = solve(i); if(A[i].k == -1) { printf("-1"); return 0; } } if(A[0].k == -233 || A[1].k == -233) { if(A[0].k == -233 && A[1].k == -233) { printf("%d", A[0].b == A[1].b ? A[0].b: -1); return 0; } else if(A[0].k == -233) { // x * A[1].k + A[1].b == A[0].b; if(A[0].b - A[1].b >= 0 && (A[0].b - A[1].b) % A[1].k == 0) { printf("%d", A[0].b); } else printf("-1"); return 0; } else { if(A[1].b - A[0].b >= 0 && (A[1].b - A[0].b) % A[0].k == 0) { printf("%d", A[1].b); } else printf("-1"); return 0; } } // A[0] and A[1] ll fin = T.solve(A[0].k, -A[1].k, A[1].b - A[0].b); if(fin == -1) printf("-1"); else { printf("%lld", fin * A[0].k + A[0].b); } return 0; }
Quantifier Question
来源:CF1344C, 2600
可以将大小关系建立一张图,显然这张图不可以有环.
考虑一个点可以被设为任意的条件:
这个点的前驱和后继不能有小于这个点的编号.
然后每种这样的点都能被取到,于是跑一个拓扑排序记录一下入队序列更新即可.
#include <cstdio> #include <queue> #include <vector> #include <cstring> #include <algorithm> #define N 200009 #define pb push_back #define ll long long #define setIO(s) freopen(s".in","r",stdin) using namespace std; int n, m, deg[N], A[N], m1[N], m2[N], cn, cnt; queue<int>q; vector<int>G[N], e[N]; int main() { // setIO("input"); scanf("%d%d", &n, &m); for(int i = 1; i <= m ; ++ i) { int x, y; scanf("%d%d", &x, &y); G[x].pb(y); ++ deg[y]; e[y].pb(x); } for(int i = 1; i <= n ; ++ i) { if(!deg[i]) { A[++ cnt] = i, q.push(i); } } while(!q.empty()) { int u = q.front(); q.pop(); for(int i = 0; i < G[u].size() ; ++ i) { int v = G[u][i]; -- deg[v]; if(!deg[v]) { A[++ cnt] = v, q.push(v); } } } for(int i = 1; i <= n ; ++ i) { m1[i] = m2[i] = i; } if(cnt < n) { printf("-1"); } else { for(int i = 1; i <= cnt ; ++ i) { int u = A[i]; // 前驱,即 y -> u for(int j = 0; j < e[u].size() ; ++ j) { int y = e[u][j]; m1[u] = min(m1[u], m1[y]); } } for(int i = cnt; i >= 1; -- i) { int u = A[i]; for(int j = 0; j < G[u].size() ; ++ j) { int y = G[u][j]; m2[u] = min(m2[u], m2[y]); } } int sc = 0; for(int i = 1; i <= n ; ++ i) { if(min(m1[i], m2[i]) >= i) { ++ sc; } } printf("%d\n", sc); for(int i = 1; i <= n ; ++ i) { if(min(m1[i], m2[i]) >= i) { printf("A"); } else { printf("E"); } } } return 0; }
Phoenix and Earthquake
来源:CF1515F, 2600
显然问题有解的必要条件是所有点权和大于等于 $\mathrm{(n-1)x}$.
然后有一个结论就是这还是一个充分条件.
证明:随便拿出 $\mathrm{n}$ 个点的生成树,并随便拿出一个叶子.
若叶子的权值大于等于 $\mathrm{x}$, 将叶子和父亲合并后对剩余 $\mathrm{n-1}$ 个块更优.
若叶子的权值小于 $\mathrm{x}$, 则先将父亲所在的 $\mathrm{n-1}$ 个点的块合并,最后合并叶子.
用数学归纳法就证完了.
然后求解的时候就按照上述方法来做,开一个队列和栈.
权值大的边加入队列,权值小的边加入栈,然后边加边更新权值即可.
#include <cstdio> #include <vector> #include <cstring> #include <queue> #include <stack> #include <algorithm> #define N 300009 #define ll long long #define pb push_back #define setIO(s) freopen(s".in","r",stdin) using namespace std; int fa[N]; int hd[N], to[ N << 1 ], nex[N << 1]; int n, m, val,from[N], vis[N], edges; ll a[N]; queue<int>q; stack<int>S; void add(int u, int v) { nex[++ edges] = hd[u]; hd[u] = edges, to[edges] = v; } vector<int>G[N]; void init() { for(int i = 0; i < N ; ++ i) fa[i] = i; } int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); } void merge(int x, int y) { x = find(x); y = find(y); if(x != y) fa[x] = y; } void dfs(int x, int ff, int id) { vis[x] = 1; for(int i = hd[x]; i ; i = nex[i]) { int v = to[i]; if(vis[v]) { continue; } // printf("%d\n", v); dfs(v, x, i); } if(x == 1) { return ; } else { if(a[x] >= val) { q.push(id), a[ff] += a[x] - val; } else { S.push(id); } } } int main() { // setIO("input"); scanf("%d%d%d", &n, &m, &val); ll sum = 0ll; for(int i = 1; i <= n ; ++ i) { scanf("%lld", &a[i]), sum += a[i]; } edges = 1; for(int i = 1; i <= m ; ++ i) { int x, y; scanf("%d%d", &x, &y); add(x, y), add(y, x); merge(x, y); } int cn = 0; for(int i = 1; i <= n ; ++ i) { if(find(i) == i) ++ cn; } if(cn > 1 || sum < 1ll * (n - 1) * val) { printf("NO"); } else { printf("YES\n"); dfs(1, 0, 0); while(!q.empty()) { printf("%d\n", q.front() >> 1); q.pop(); } while(!S.empty()) { printf("%d\n", S.top() >> 1); S.pop(); } } return 0; }
Digit Tree
来源:CF715C, 2800
路径问题考虑用点分治进行处理.
考虑当前的分治中心 $\mathrm{x}$, 对于两条路径 $\mathrm{d1,d2}$ 如何合并.
令 $\mathrm{d1,d2}$ 分别表示从下到上,从上到下的路径.
然后$\mathrm{d1}$ 和 $\mathrm{d2}$ 能合并的条件是 $\mathrm{d1=-d2 \times 10^{-dep2}}$.
有这个式子后开两个 $\mathrm{map}$ 分别进行统计即可.
这里注意这个 $\mathrm{m}$ 不一定是质数,所以需要用扩展欧几里得算法求逆元.
#include <cstdio> #include <map> #include <vector> #include <cstring> #include <algorithm> #define N 100009 #define ll long long #define setIO(s) freopen(s".in","r",stdin) using namespace std; ll ans; int n, mod, inv, hd[N], to[N << 1], nex[N << 1], val[N << 1], edges, sn, root; int f[N], size[N], vis[N], dep[N], bu[N], base[N]; map<int, int>c1, c2; ll exgcd(ll a, ll b, ll &x, ll &y) { if(!b) { x = 1, y = 0; return a; } ll gc = exgcd(b, a % b, x, y); ll t = x; x = y, y = t - (a / b) * y; return gc; } void init() { ll x0, y0; exgcd(10, mod, x0, y0); x0 = (x0 % mod + mod) % mod; inv = (int)x0; } void add(int u, int v, int c) { nex[++edges] = hd[u]; hd[u] = edges, to[edges] = v, val[edges] = c; } void getroot(int x, int ff) { size[x] = 1, f[x] = 0; for(int i = hd[x]; i ; i = nex[i]) { int v = to[i]; if(v == ff || vis[v]) continue; getroot(v, x), size[x] += size[v]; f[x] = max(f[x], size[v]); } f[x] = max(f[x], sn - f[x]); if(f[x] < f[root]) root = x; } // 第一波. void calc(int x, int ff, int d1, int d2) { dep[x] = dep[ff] + 1; // 考虑 (d1, d2) 对前面的贡献. ans += c2[d1]; ans += c1[(ll)(mod - d2) * bu[dep[x]] % mod]; for(int i = hd[x]; i ; i = nex[i]) { int v = to[i]; if(v == ff || vis[v]) { continue; } calc(v, x, (ll)(d1 + (ll)base[dep[x]] * val[i] % mod) % mod, (ll)((ll)d2 * 10 % mod + val[i]) % mod); } } // 第二波. void getdis(int x, int ff, int d1, int d2) { dep[x] = dep[ff] + 1; if(!d1) ++ ans; if(!d2) ++ ans; c1[d1] ++ ; c2[(ll)(mod - d2) * bu[dep[x]] % mod] ++ ; // c1 = d1 // c2 = -d2 * 10 ^ (-dep) for(int i = hd[x]; i ; i = nex[i]) { int v = to[i]; if(v == ff || vis[v]) { continue; } getdis(v, x, (ll)(d1 + (ll)base[dep[x]] * val[i] % mod) % mod, (ll)((ll)d2 * 10 % mod + val[i]) % mod); } } void dfs(int x) { vis[x] = 1; for(int i = hd[x]; i ; i = nex[i]) { int v = to[i]; if(vis[v]) continue; dep[x] = 0; calc(v, x, val[i] % mod, val[i] % mod); getdis(v, x, val[i] % mod, val[i] % mod); } // 当前计算完毕. c1.clear(); c2.clear(); for(int i = hd[x]; i ; i = nex[i]) { int v = to[i]; if(vis[v]) continue; root = 0, sn = size[v], getroot(v, 0); dfs(root); } } int main() { // setIO("input"); scanf("%d%d", &n, &mod); init(); for(int i = 1; i < n ; ++ i) { int x, y, z; scanf("%d%d%d", &x, &y, &z); ++ x, ++ y; add(x, y, z), add(y, x, z); } bu[0] = 1, base[0] = 1; for(int i = 1; i < N ; ++ i) { bu[i] = (ll)bu[i - 1] * inv % mod; base[i] = (ll)base[i - 1] * 10 % mod; } f[root = 0] = N, sn = n, getroot(1, 0); dfs(root); printf("%lld", ans); return 0; }