牛客周赛 Round 62 全部题
比赛链接
A 小红的字符移动
statement: 有一个 5 个字符的串,交换前两个字符输出。
solution: 直接模拟即可。由于过于简单就不放代码了。
B 小红的数轴移动
statement: 对于序列 $a_1,a_2,...,a_n$,小红一开始在 $x$,每次向原点的方向移动 $a_i$,如果到原点就停止。问一共走了多少路程。
solution: 依旧按题意模拟即可。
Code
int n, x, a[100010], ans; int main(){ cin >> n >> x; for(int i = 1; i <= n; i++) cin >> a[i]; int i = 1; while(x != 0){ if(x > 0) ans += a[i], x -= a[i]; else ans += a[i], x += a[i]; i++; if(i > n) break; } cout << ans << endl; return 0; }
C 小红的圆移动
statement:
平面上有 $n$ 个圆。小红可以进行任意次操作,每次操作可以选择一个圆,将它向任意方向移动若干距离。该操作的代价为该圆面积乘以移动的距离。
小红希望最终包含原点的圆数量不超过 $k$,请你帮小红算出她操作的最小总代价。$1 \le k \le n \le 10^5$。
Solution:
先算出每个圆的面积,第 $i$ 个圆需要移动的距离等于 $\max(0,r_i-\sqrt{x_i^2+y_i^2})$。
相乘后排序,把最小的 $n-k$ 个数加起来即可。
Code
int n, k; double x[100010], y[100010], r[100010], S[100010], ans; int main(){ cin >> n >> k; for(int i = 1; i <= n; i++){ cin >> x[i] >> y[i] >> r[i]; S[i] = 1.0 * acos(-1) * r[i] * r[i]; S[i] = S[i] * (r[i] - sqrt(x[i] * x[i] + y[i] * y[i])); if(S[i] < 0.0) S[i] = 0.0; } sort(S + 1, S + n + 1); for(int i = n - k; i >= 1; i--) ans += S[i]; printf("%.10lf\n", ans); return 0; }
D 小红的树上移动
statement:
小红拿到了一棵 $n$ 个节点的树,初始所有节点均为白色。小红初始站在1号节点,并将1号节点染红。小红将不断进行移动直到停止:
1. 若当前节点的邻点中存在白色节点,则小红随机移动到某个相邻的白色节点,并将该节点染红。
2. 若当前节点的邻点中不存在白色节点,则停止移动。
请你帮小红求出最终红点数量的期望。答案在模 $10^9+7$ 意义下计算。$1 \le n \le 10^5$。
solution:
观察到从 $1$ 出发到每个点的路径是唯一的。所以,答案等于每个点到达的概率(设为 $p_i$)之和。
以 $1$ 为根,建一棵有根树。然后对于每个节点 $i$,统计它的儿子数量 $sz(i)$。这样对于节点 $i$ 的儿子 $j$,有 $p_j=\frac{p_i}{sz(i)}$。
DFS 一遍,算概率的时候求个逆元,最后累加起来就是答案了。
Code:
#define int long longconst int INF = 1e9, MOD = 1000000007; int n, s[100010], ans; vector <int> e[100010]; int qpow(int a, int b){ int t = 1; while(b){ if(b & 1) t = t * a % MOD; a = a * a % MOD; b >>= 1; } return t; } void dfs(int x, int fa, int p){ ans = (ans + p) % MOD; for(int i : e[x]) if(i != fa) s[x]++; int t = qpow(s[x], MOD - 2); for(int i : e[x]) if(i != fa){ dfs(i, x, p * t % MOD); } } signed main(){ cin >> n; for(int i = 1, u, v; i < n; i++){ cin >> u >> v; e[u].push_back(v); e[v].push_back(u); } dfs(1, 0, 1); cout << ans << endl; return 0; }
E&F 小红的中位数查询
statement:
有一个序列 $a_1,a_2,...a_n$,$q$ 次查询,每次查询一个长度为奇数的子区间 $l,r$ 的中位数。
$1 \le n,q \le 10^5$,$1 \le a_i \le 10^9$。
solution 1:
先把 $a_i$ 离散化(但要输出的是原值)假设权值的区间是 $1 \sim m$,然后对于每个权值记 vector:$v_1,v_2,...,v_m$,记录所有等于它的数的下标。
一种暴力的思想是,把 $i=1\sim m$ 每个数都枚举一次,每次二分求出区间 $[l,r]$ 内数字 $i$ 的个数。
然后不断累加,直到数字个数大于等于 $\frac{r-l+2}{2}$。
但是这么做的时间复杂度是 $\mathcal{O}(n^2 \log n)$ 的,太慢了。
但我们可以考虑分块!以 $\sqrt{m}$ 为阈值,假设块长 $L$。
大块的部分记录权值分别在 $[1,L],[L+1,2L],...$ 之中的数字个数随着下标增加的前缀和。
小块的部分和上面的暴力想法一样。
对于每次询问,我们一个个大块跑过去,判断加到哪个块的时候,累计的数字个数大于等于 $\frac{r-l+2}{2}$,记录这个块的左右端点 $p,p+L-1$。
然后我们让 $i$ 从 $p+L-1$ 往 $p$ 减少,每次删掉区间 $[l,r]$ 中 $i$ 的出现次数,直到不满足数字个数大于等于 $\frac{r-l+2}{2}$,这个区间的中位数就是 $i$ 了。
时间复杂度 $\mathcal{O}(n \sqrt{n} \log n)$,可以勉强通过。
Code
#include <bits/stdc++.h> #define int long long using namespace std; const int INF = 1e9;
int n, m, q, sz, blk[100010], mm, s[410][100010]; int a[100010], b[100010], t[400010], l[100010], r[100010]; vector <int> v[100010];
signed main(){ std::ios::sync_with_stdio(false); std::cin.tie(0); cin >> n >> q; for(int i = 1; i <= n; i++) cin >> a[i], b[i] = a[i]; sort(b + 1, b + n + 1); m = unique(b + 1, b + n + 1) - b - 1; sz = (int)sqrt(m); for(int i = 1; i <= n; i++){ a[i] = lower_bound(b + 1, b + m + 1, a[i]) - b; blk[i] = (i - 1) / sz + 1; v[a[i]].push_back(i); } mm = blk[n]; for(int i = 1; i <= n; i++) s[blk[a[i]]][i]++; for(int i = 1; i <= mm; i++) for(int j = 1; j <= n; j++) s[i][j] += s[i][j - 1]; for(int i = 1, now, L, R; i <= n; i++){ cin >> l[i] >> r[i], now = 0; for(int j = 1; j <= mm; j++){ now += s[j][r[i]] - s[j][l[i] - 1]; if(now > (r[i] - l[i] + 1) >> 1){ L = (j - 1) * sz + 1; R = j * sz; break; } } if(now <= (r[i] - l[i] + 1) >> 1) continue; for(int j = R, p1, p2; j >= L; j--){ p2 = upper_bound(v[j].begin(), v[j].end(), r[i]) - v[j].begin(); p1 = lower_bound(v[j].begin(), v[j].end(), l[i]) - v[j].begin(); now -= p2 - p1; if(now <= (r[i] - l[i] + 1) >> 1){ printf("%d\n", b[j]); break; } } } return 0; }
Solution 2
莫队 + 值域分块,$\mathcal{O}(n \sqrt{n})$
Solution 3
主席树,暂时没写,可以去看其它博客,$\mathcal{O}(n \log^2 n)$。
G 小红的数轴移动(二)
statement:
对于序列 $a_1,a_2,...,a_n$,小红一开始在 $x$,每次向原点的方向依次移动 $a_i$,如果到原点就停止。
你现在可以改变 $a_i$ 的顺序,问最少要走多少路程,并输出方案。
$1 \le n,a_i \le 100$。
solution
直接模拟退火,多调几次参数就过了。
下面这份代码可能你再交不一定是满分,但多交几次总可以的(
Code
#include <bits/stdc++.h> using namespace std; mt19937 rnd(time(nullptr)); const double eps = 1e-8; int n, X, now, A[110], P[110], ANS = 100000, a[110], p[110], b[110]; double T; void sa(){ for(int i = 1; i <= n; i++) p[i] = i; shuffle(p + 1, p + n + 1, rnd); for(int i = 1; i <= n; i++) a[i] = A[p[i]]; double T = 3000.0, d; int ss, delta, sum, ans = 100000; while(T > eps){ T *= 0.98; sum = 0; int x = rnd() % n + 1, y = rnd() % n + 1; swap(a[x], a[y]), swap(p[x], p[y]); now = X; for(int i = 1; i <= n; i++){ if(now == 0) break; if(now > 0) now -= a[i], sum += a[i]; else now += a[i], sum += a[i]; } delta = ans - sum; if(delta > 0){ ans = sum; } else{ if(exp(1.0 * (-delta) / T) * RAND_MAX > rand()) swap(a[x], a[y]), swap(p[x], p[y]); } } if(ANS > ans){ ANS = ans; for(int j = 1; j <= n; j++) P[j] = p[j]; } } int main(){ double beg = clock(); cin >> n >> X; for(int i = 1; i <= n; i++) cin >> a[i], A[i] = a[i], p[i] = i; while((clock() - beg) / CLOCKS_PER_SEC < 0.84) // 卡时 sa(); cout << ANS << endl; for(int i = 1; i <= n; i++) cout << P[i] << " "; cout << endl; return 0; }