2021 牛客寒假多校1 神崎兰子 构造 思维
串
DP。雨神的博客讲得很清楚。
我也做了一点注释。
#include <bits/stdc++.h> #define sc(x) scanf("%lld", &(x)) #define pr(x) printf("%lld\n", (x)) #define rep(i, l, r) for (int i = (l); i <= (r); ++i) using namespace std; typedef long long ll; const int N = 1e6 + 7; const int mod = 1e9 + 7; ll dp[N][3]; int n; int main() { cin >> n; dp[1][0] = 25; // dp[i][0]表示长度i无u的串数量 dp[1][1] = 1; // dp[i][1]表示长度i有u无us的串数量 dp[1][2] = 0; // dp[i][2]表示长度i有us的串 ll ans = 0; rep(i, 2, n) { dp[i][0] = (dp[i - 1][0] * 25) % mod; //在前串尾缀除u以外的字符即可 dp[i][1] = (dp[i - 1][1] * 25 + dp[i - 1][0]) % mod; //有u串尾缀非s + 无u串尾缀u dp[i][2] = (dp[i - 1][1] + dp[i - 1][2] * 26) % mod; //无s串尾缀s + 符合串尾缀任意字符 ans = (ans + dp[i][2]) % mod; } cout << ans; return 0; }
括号
构造长度小于的有k个合法括号对的括号字符串。
首先考虑形如这样的括号字符串
(((()))))
它有n个左括号,m个右括号,当给定的k很大的时候,如果能将括号字符串表示成这种形式是最短的:由均值不等式,当n和m尽可能接近的时候,字符串会最短
k最大可达
,
可满足长度需求
考虑k为一个大质数,无法满足成两个数的乘积形式,如何解决?观察括号字符串,当在从左往右数第i个左括号后添加一个右括号,可以使得括号字符串的合法括号对增加i个。
(()(()))))
增加两个合法括号对。
综上,可以考虑转化成这样式子
,且为了使得n*m尽可能接近,可令
#include <bits/stdc++.h> #define sc(x) scanf("%lld", &(x)) #define pr(x) printf("%lld\n", (x)) #define rep(i, l, r) for (int i = (l); i <= (r); ++i) using namespace std; typedef long long ll; const int N = 1e5; const int mod = 1e9 + 7; int main() { ll n; sc(n); if (n == 0) return puts(")("), 0; ll m = sqrt(n); ll r = n / m, d = n % m; rep(i, 1, m) { putchar('('); if (i == d) putchar(')'); } rep(i, 1, r) putchar(')'); return 0; }
红与蓝
- 叶子节点之与父亲有边相连,所以叶子节点必然与父亲同色。
- 而父亲节点已经和叶子节点同色,所以叶子节点必然与爷爷节点异色。
- 爷爷的颜色确定后,如果爷爷还与一条边相连,那么标记爷爷的相邻点颜色也确定。
- 所以统计位置信息然后再跑一边DFS染色即可
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1E5 + 10; ll read() { ll a = 0; char b = 1, c; do if ((c = getchar()) == 45) b = -1; while (c < 48 || c > 57); do a = (a << 3) + (a << 1) + (c & 15); while ((c = getchar()) > 47 && c < 58); return a * b; } int head[N], nex[N << 1], to[N << 1]; int cnt; int fri[N]; bitset<N> b; void add(int u, int v) { nex[++cnt] = head[u]; head[u] = cnt; to[cnt] = v; } void dfs1(int x, int f) { for (int i = head[x]; i; i = nex[i]) { int t = to[i]; if (t == f) continue; dfs1(t, x); if (!fri[x] && !fri[t]) { fri[x] = t; fri[t] = x; } } } void dfs2(int x, int f) { for (int i = head[x]; i; i = nex[i]) { int t = to[i]; if (t == f) continue; b[t] = t == fri[x] ? b[x] : !b[x]; dfs2(t, x); } } int main() { int n = read(); for (int i = 1; i < n; ++i) { int u = read(), v = read(); add(u, v); add(v, u); } dfs1(1, 0); if (!*min_element(fri + 1, fri + 1 + n)) { puts("-1"); return 0; } dfs2(1, 0); for (int i = 1; i <= n; ++i) putchar(b[i] ? 'R' : 'B'); }
点零成一
本题其实是一个比较简单的并查集,但是本菜鸡太久没写了以至于debug了很久。
数据量很小于是可以暴力合并,新加进来的数也可以实时合并或增加集合。
答案其实就是每个集合里面的点数累乘,最后乘一个集合数量的全排列即可。
#include <bits/stdc++.h> #define sc(x) scanf("%d", &(x)) #define pr(x) printf("%lld\n", (x)) #define rep(i, l, r) for (int i = (l); i <= (r); ++i) #define pos(x, y) ((x - 1) * n + (y)) #define out(x, y) ((x) < 1 || (x) > n || (y) < 1 || (y) > n) using namespace std; typedef long long ll; const int N = 500 + 7; const int mod = 1e9 + 7; ll qkpow(ll a, ll b) { ll ans = 1; while (b) { if (b & 1) ans = ans * a % mod; a = a * a % mod; b >>= 1; } return ans; } ll getInv(ll a) { return qkpow(a, mod - 2); } //求一个数的逆元 int sz[N * N]; int fa[N * N]; inline void init(int n) { for (int i = 0; i <= n; ++i) fa[i] = i, sz[i] = 1; } inline int Find(int x) { if (x != fa[x]) fa[x] = Find(fa[x]); //路径压缩 return fa[x]; } void merge(int x, int y) { int fx = Find(x); int fy = Find(y); if (fx != fy) fa[fx] = y, sz[fy] += sz[fx]; } int n; char g[N][N]; bool vis[N][N]; int dx[] = {1, -1, 0, 0}; int dy[] = {0, 0, 1, -1}; void dfs(int x, int y, int fa) { if (out(x, y)) return; vis[x][y] = 1; if (pos(x, y) != fa) merge(pos(x, y), fa); rep(i, 0, 3) { if (g[x + dx[i]][y + dy[i]] == '1' && !vis[x + dx[i]][y + dy[i]]) dfs(x + dx[i], y + dy[i], fa); } } ll ans = 1; ll cnt = 0; //集合数量 void chk(int x, int y) { if (g[x][y] == '1') return; g[x][y] = '1'; ++cnt; ans = ans * cnt % mod; int now = pos(x, y); rep(i, 0, 3) { if (!out(x + dx[i], y + dy[i]) && g[x + dx[i]][y + dy[i]] == '1') { int nxt = pos(x + dx[i], y + dy[i]); int f1 = Find(now); int f2 = Find(nxt); if (f1 != f2) { ans = ans * getInv(cnt) % mod; ans = ans * getInv(sz[f1]) % mod; ans = ans * getInv(sz[f2]) % mod; ans = ans * (sz[f1] + sz[f2]) % mod; --cnt; merge(f1, f2); } } } } int main() { sc(n); init(n * n); rep(i, 1, n) scanf(" %s", g[i] + 1); rep(i, 1, n) rep(j, 1, n) { if (g[i][j] == '1') { int now = pos(i, j); rep(z, 0, 3) { int nxt = pos(i + dx[z], j + dy[z]); if (!out(i + dx[z], j + dy[z]) && g[i + dx[z]][j + dy[z]] == '1') merge(now, nxt); } } } rep(i, 1, n) rep(j, 1, n) { int now = pos(i, j); if (g[i][j] == '1' && Find(now) == now) ++cnt, ans = (ans * sz[now]) % mod; } rep(i, 1, cnt) ans = ans * i % mod; int t, u, v; sc(t); while (t--) { sc(u), sc(v); chk(++u, ++v); pr(ans); } return 0; }
三棱锥之刻
根据发射距离R的不同,可分为四种情况
- 碰不到
- 四个圆
- 圆和三角的交*4
- 正四面体的全部内表面
具体来说就是计算小三角形的面积,再加上一个最小的扇形面积。
重合部分的面积等于
from math import sqrt, acos, pi a, R = map(float, input().split()) d = sqrt(6) * a / 12 #中心到面的距离 r = sqrt(R * R - d * d) if d < R else 0 #三角形上的圆的半径 if R <= d: ans = 0 #碰不到 elif r <= a / (sqrt(3) * 2): #四个圆 ans = 4 * pi * (R * R - d * d) elif R >= a * sqrt(6) / 4: #全覆盖 ans = a * a * sqrt(3) else: #切掉 DF = a / (2 * sqrt(3)) GF = sqrt(r * r - DF * DF) #求底面三角形 半底长 deg = pi / 3 - acos(DF / r) # 小扇形的弧度 ans = GF * DF * 3 shan = deg * 0.5 * r * r ans += 6 * shan ans *= 4 #就漏了这一句 print(ans)
对答案一时爽
两人成绩最大值:保证每题至少有一人做对
最小值:保证没有一人做对,因为两个人无法覆盖四个选项,直接就是0
n = int(input()) a = input() b = input() a = a.replace(' ', '') b = b.replace(' ', '') cnt = 0 for i in range(n): if a[i] == b[i]: cnt += 1 print(cnt + n, 0)
好玩的数字游戏
本题是一个大模拟,但是比赛的时候把时间分配给大模拟其实是一件很有风险的事情,尤其是在很有机会的题目还没有解决的时候。
#include <bits/stdc++.h> #define sc(x) scanf("%lld", &(x)) #define pr(x) printf("%lld\n", (x)) #define rep(i, l, r) for (int i = (l); i <= (r); ++i) using namespace std; typedef long long ll; const int N = 1e5 + 7; const int mod = 1e9 + 7; //refer : https://ac.nowcoder.com/acm/contest/view-submission?submissionId=46663438 ll x0, p, q, s = 0, ed = -1; ll a[6][6]; char seq[N << 1]; inline ll f(const ll &x, const ll &p) { ll r = (x % p) * (x % p) / 10 % p; return (r ^ (1LL << 17) ^ (1LL << 57)) % p; } inline int mergeCol(const int &x, const int &d) { int res = 0; if (d > 0) { for (int i = 4, j; i >= 2; --i) { if (a[i][x]) { for (j = i - 1; j >= 1 && a[j][x] == 0; --j) ; if (a[j][x] == a[i][x]) { a[i][x] <<= 1, a[j][x] = 0; s += a[i][x], res = 1; } } } for (int i = 4, j; i >= 2; --i) { if (!a[i][x]) { for (j = i - 1; j >= 1 && a[j][x] == 0; --j) ; if (a[j][x]) a[i][x] = a[j][x], a[j][x] = 0, res = 1; } } } else { for (int i = 1, j; i <= 3; ++i) { if (a[i][x]) { for (j = i + 1; j <= 4 && a[j][x] == 0; ++j) ; if (a[j][x] == a[i][x]) { a[i][x] <<= 1, a[j][x] = 0; s += a[i][x], res = 1; } } } for (int i = 1, j; i <= 3; ++i) { if (!a[i][x]) { for (j = i + 1; j <= 4 && a[j][x] == 0; ++j) ; if (a[j][x]) a[i][x] = a[j][x], a[j][x] = 0, res = 1; } } } return res; } inline int mergeRow(const int &x, const int &d) { int res = 0; if (d > 0) { for (int i = 4, j; i >= 2; --i) { if (a[x][i]) { for (j = i - 1; j >= 1 && a[x][j] == 0; --j) ; if (a[x][j] == a[x][i]) { a[x][i] <<= 1, a[x][j] = 0; s += a[x][i], res = 1; } } } for (int i = 4, j; i >= 2; --i) { if (!a[x][i]) { for (j = i - 1; j >= 1 && a[x][j] == 0; --j) ; if (a[x][j]) a[x][i] = a[x][j], a[x][j] = 0, res = 1; } } } else { for (int i = 1, j; i <= 3; ++i) { if (a[x][i]) { for (j = i + 1; j <= 4 && a[x][j] == 0; ++j) ; if (a[x][j] == a[x][i]) { a[x][i] <<= 1, a[x][j] = 0; s += a[x][i], res = 1; } } } for (int i = 1, j; i <= 3; ++i) { if (!a[x][i]) { for (j = i + 1; j <= 4 && a[x][j] == 0; ++j) ; if (a[x][j]) a[x][i] = a[x][j], a[x][j] = 0, res = 1; } } } return res; } inline int move() { for (int i = 1, j; i <= 4; ++i) for (j = 1; j <= 4; ++j) { if (!a[i][j]) return 1; if (a[i][j] == a[i - 1][j]) return 1; if (a[i][j] == a[i][j - 1]) return 1; } return 0; } inline void update() { while (1) { int tmp = x0 % 16; x0 = f(x0, p); if (!a[tmp / 4 + 1][tmp % 4 + 1]) { a[tmp / 4 + 1][tmp % 4 + 1] = 2; break; } } } int main() { scanf("%lld%lld%lld", &x0, &p, &q); for (int i = 1, j; i <= 4; ++i) for (j = 1; j <= 4; ++j) scanf("%lld", &a[i][j]); scanf("%s", seq + 1); for (int i = 1, d, x; i <= q; ++i) { x = seq[2 * i] - '0'; if (seq[2 * i - 1] == 'W' || seq[2 * i - 1] == 'S') { d = seq[2 * i - 1] == 'W' ? -1 : 1; if (mergeCol(x, d)) update(); } else { d = seq[2 * i - 1] == 'A' ? -1 : 1; if (mergeRow(x, d)) update(); } if (!move()) { ed = i; break; } } printf("%lld\n", s); if (ed == -1) puts("never die!"); else printf("%lld\n", ed); return 0; }
幂塔个位数的计算
本题可以找规律,但这里采用欧拉降幂的做法:
欧拉降幂的前置知识是欧拉函数
简单来说欧拉降幂其实就是一个公式
表示同余,
表示p的欧拉函数
直接套公式就可以写了,Python的int是会处理大数的。
def phi(x): if x == 10: return 4 if x == 4: return 2 if x == 2: return 1 if x == 1: return 1 def cal(a, n, mod): if (mod == 1): return 1 if n == 1: return a % mod + mod return pow(a, cal(a, n - 1, phi(mod)), mod) + mod a = int(input()) n = int(input()) if (n == 1): print(a % 10) else: print(pow(a, cal(a, n - 1, phi(10)), 10))
下面这种做法则是直接找出循环节
a = int(input()) n = int(input()) if (n == 1): print(a % 10) # n==1 直接输出个位数 elif (n == 2): print(pow(a % 10, a, 10)) # n==2 直接快速幂算 else: e = pow(a % 4, a % 2 + 2, 4) print(pow(a % 10, e + 4, 10))
限制不互素对的排列
由于保证了,于是当
时,选择全部的偶数对顺序排列即可得到答案。而当{k= \frac{n}{2}}$时只需要将6和3放在一起即可。
#include <bits/stdc++.h> #define sc(x) scanf("%d", &(x)) #define pr(x) printf("%d ", (x)) #define rep(i, l, r) for (int i = (l); i <= (r); ++i) using namespace std; typedef long long ll; const int N = 1e5 + 7; const int mod = 1e9 + 7; bool vis[N]; int ans[N], p = 1; int main() { int n, k; sc(n), sc(k); ans[p++] = 2, vis[2] = 1; int cnt = 0; rep(i, 1, k) { int now = 2 * (i + 1); if (now > n) break; ans[p++] = now; vis[now] = 1; ++cnt; } if (n >= 6 and cnt < k) swap(ans[3], ans[p - 1]), ans[p++] = 3, vis[3] = 1, ++cnt; if (cnt < k) puts("-1"); else { rep(i, 1, p - 1) pr(ans[i]); rep(i, 1, n) if (!vis[i]) pr(i); } return 0; }
一群小青蛙呱蹦呱蹦呱
题意是求以内的所有非单因子数的最小公倍数。
仔细思考可以想到2贡献了次,因为有它因子的,包括它的尽可能高的次幂的数就是
了。
同理其他非2的质数x贡献了次。
看来还是我的埃筛常数优秀。
#pragma GCC target("avx,sse2,sse3,sse4,popcnt") #pragma GCC optimize("O2,O3,Ofast,inline,unroll-all-loops,-ffast-math") #include <bits/stdc++.h> #define sc(x) scanf("%lld", &(x)) #define pr(x) printf("%lld\n", (x)) using namespace std; typedef long long ll; const int N = 8e7 + 7; const int mod = 1e9 + 7; bool notprime[N]; int primes[N], pcnt = 0; inline void pre() { notprime[1] = 1; for (int i = 2, n = sqrt(N) + 1; i <= n; ++i) { if (!notprime[i]) { for (int j = N / i; j >= i; --j) { if (!notprime[j]) notprime[i * j] = 1; } } } primes[++pcnt] = 2; for (int i = 3; i < N; ++i) if (!notprime[i]) primes[++pcnt] = i; } ll qkpow(ll a, ll b) { ll ans = 1; while (b) { if (b & 1) ans = ans * a % mod; a = a * a % mod; b >>= 1; } return ans; } ll getInv(ll a) { return qkpow(a, mod - 2); } //求一个数的逆元 inline ll read() { ll s = 0, f = 1; char ch; do { ch = getchar(); if (ch == '-') f = -1; } while (ch < 48 || ch > 57); while (ch >= 48 && ch <= 57) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar(); return s * f; } int main() { pre(); ll n = read(); ll tm = log2(n / 3); ll ans = qkpow(2, tm); for (int i = 2; i <= pcnt; ++i) { if (2 * primes[i] <= n) { tm = log(n / 2) / log(primes[i]); ans = (ans * qkpow(primes[i], tm)) % mod; } else break; } if (ans == 1) puts("empty"); else pr(ans); return 0; }