HUAWEI Programming Contest 2024(AtCoder Beginner Contest 342)
黑白可能不适应,右下角可调背景,黑白转换。
C
如果做过最初分块肯定是秒了(我可能一辈子都不会学),如果用线段树做过类似的题也应该知道定义 \(cover[i]\) 表示原来的 \(i\) 现在变成了 \(cover[i]\)。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5, M = 27;
int n, m, cover[100];
char a[N];
int main() {
cin >> n >> (a + 1);
for (int i = 0; i < 26; i++) cover[i] = i;
cin >> m;
while (m--) {
char x, y;
cin >> x >> y;
for (int i = 0; i < 26; i++) {
if (cover[i] == x - 'a') cover[i] = y - 'a';
}
}
for (int i = 1; i <= n; i++) cout << char(cover[a[i] - 'a'] + 'a');
}
D
先将所有数写成唯一分解形式,去掉平方因子。
观察到如果 \(d^2=a\times b\),那么 \(a\) 去掉平方因子后和 \(b\) 去掉平方因子的数相等。简单证明:
- 若 \(a,b\) 都为平方数,去掉平方因子后都为 \(1\)。
- 否则,\(a,b\) 就都不为平方数,如果乘积为平方数,则表示这两个数的奇数次幂因子互补,则奇数次幂都为 \(1\)。
那么我们开个桶来存储,计算之前有多少个数去掉平方因子后和该数相等即可。注意特殊处理 \(0\)。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3e5 + 5, base = 131;
int n, a[N], vis[N], prime[N];
pair<int, int> tt[N];
int len;
int check(int x) {
if (x == 2) return 1;
for (int i = 2; i <= x / i; i++) {
if (x % i == 0) return 0;
}
return 1;
}
signed main() {
cin >> n;
int ans = 0;
for (int i = 1; i <= n; i++) cin >> a[i];
int cnt = 0;
for (int i = 1; i <= n; i++) {
if (a[i] == 0) { // 特殊处理 0
ans += n - (cnt + 1);
cnt++;
continue;
}
int t = a[i]; len = 0;
for (int j = 2; j <= t / j; j++) {
if (t % j == 0) {
int cnt = 0;
while (t % j == 0) t /= j, cnt++;
tt[++len] = {j, cnt % 2};
}
}
if (t > 1) tt[++len] = {t, 1};
int res = 1;
for (int j = 1; j <= len; j++) {
if (tt[j].second) res = res * tt[j].first;
}
ans += vis[res];
vis[res]++;
}
cout << ans << endl;
}
我的代码肯定不是最简化的,事实上,可以牺牲一点效率,直接枚举所有平方数,让 \(a_i\) 除以他们(当然要求没有余数),就是去掉平方因子后的数。
E
要求出所有 \(i\) 到 \(n\) 的最短路,显然思路是从 \(n\) 开始跑反图。可以搭配 dijkstra 算法。
但是遇到了一个问题,初始 \(n\) 的时间设置为多少?\(0\) 吗?事实上,我们可以将此设置为正无穷。
对于当前点 \(u\),存在反图上的边 \(u\to v\),找到可以从 \(v\) 出发可以到达 \(u\) 最晚的一班火车。首先,如果这趟车 \(l+c>f_u\),那么这趟车是上不去的。否则,我们找到的车次 \(t\) 就是 \(\min\{\lfloor \dfrac{f_u-l-c}{d}\rfloor,k-1\}\)。更新 \(f_v=l+t\times d\)。
#include <bits/stdc++.h>
using namespace std;
#define PII pair<int, int>
#define int long long
const int N = 3e5 + 5;
int n, m, dist[N], st[N];
struct node {
int v, l, d, k, c;
};
vector<node> G[N];
void dijstra() {
fill(dist + 1, dist + n + 1, -2e18);
dist[n] = 2e18;
priority_queue<PII> q;
q.push({dist[n], n});
while (q.size()) {
auto t = q.top(); q.pop();
int u = t.second;
for (auto e : G[u]) {
int v = e.v, l = e.l, d = e.d, k = e.k, c = e.c;
if (l + c > dist[u]) continue;
int D = l + min((dist[u] - l - c) / d, k - 1) * d;
if (dist[v] < D) {
dist[v] = D;
q.push({dist[v], v});
}
}
}
}
signed main() {
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int l, d, k, c, u, v;
cin >> l >> d >> k >> c >> u >> v;
G[v].push_back({u, l, d, k, c});
}
dijstra();
for (int i = 1; i < n; i++) {
if (dist[i] == -2e18) puts("Unreachable");
else cout << dist[i] << endl;
}
}
F
定义 \(dp_i\) 表示 \(x\) 当前为 \(i\) 时获胜的概率。
有两种选择,投骰子,停止。则 \(dp_i=\max\{\dfrac{\sum_{j=i+1}^{i+D}dp_j}{D},\text{slove(i)}\}\)。其中 \(\text{slove(i)}\) 表示 \(x\) 在 \(i\) 的时候停止操作,\(y\) 接下来操作一波后,获胜的概率。
令 \(g_i\) 表示最后 \(y\) 为 \(i\) 的概率,\(sum_i\) 表示 \(g_i\) 的前缀和。那么 \(\text{slove(i)}\) 有两种情况能赢:
- 如果 \(y\) 越界了 \(y>N\),则获胜,概率为 \(1-sum_N\)。
- 如果 \(y<x\),则获胜,概率为 \(sum_{i-1}\)。
那么 \(g\) 数组如何计算?初始时 \(g_0=1\),代码说可能更直接:
g[0] = 1.0;
for (int i = 0; i <= MAXN; i++) {
if (i < L) {
for (int j = i + 1; j <= i + D; j++) g[j] += g[i] / D;
g[i] = 0;
}
}
直接这样做会超时,可以用线段树维护,单点查询,区间修改。
代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 4e5 + 5;
int n, l, d;
double g[N], dp[N], sum[N];
struct fenwick {
double c[N][2];
void mo(int x, double v) {
x += 2;
for (int i = x; i <= N - 5; i += i & -i) {
c[i][0] += v;
c[i][1] += x * v;
}
}
void modify(int l, int r, double v) {
mo(l, v);
mo(r + 1, -v);
}
double get_sum(int op, int x) {
x += 2;
double res = 0.0;
for (int i = x; i; i -= i & -i) res += c[i][op];
return res;
}
double query(int l, int r) {
double t1 = get_sum(0, l - 1) * 1.0 * (l + 2) - get_sum(1, l - 1);
double t2 = get_sum(0, r) * (1.0 * r + 3) - get_sum(1, r);
return t2 - t1;
}
}tr;
double slove(int x) {
if (x > n) return 0.0;
double res = 1 - g[n];
if (x >= 1) res += g[x - 1];
return res;
}
signed main() {
cin >> n >> l >> d;
g[0] = 1.0; tr.modify(0, 0, 1.0);
for (int i = 0; i <= N - 5; i++) {
double t = tr.query(i, i); g[i] = t;
if (i < l) {
tr.modify(i + 1, i + d, t / d);
g[i] = 0.0;
}
}
// for (int i = 0; i <= 10; i++) cout << g[i] << ' ';
// puts("");
for (int i = 1; i <= N - 5; i++) g[i] += g[i - 1];
for (int i = N - 5; i >= 0; i--) {
if (i > n) dp[i] = 0.0;
else dp[i] = max((sum[i + 1] - sum[i + d + 1]) / d, slove(i));
sum[i] = sum[i + 1] + dp[i];
}
printf("%.10lf", dp[0]);
}
G
线段树套 multiset
。对于区间 \([l,r]\) 执行 \(\max\{a_i,x\}\) 操作来讲,我们 \([l,r]\) 包含的线段树区间内,将 multiset
中加入 \(x\)。
删除第 \(i\) 次,只需要记录第 \(i\) 个操作的 \(l_i,r_i,x_i\),仿照上面的操作,只需要将 multiset
中删除 \(x_i\)。
查询第 \(x\) 个元素,在线段树递归过程中,取所有 multiset
的最大数。
#include <bits/stdc++.h>
using namespace std;
#define ls p << 1
#define rs p << 1 | 1
#define int long long
const int N = 3e5 + 5;
int n, a[N], l[N], r[N], v[N], idx;
struct edge {
int l, r;
multiset<int> s;
}tree[N * 4];
void build(int p, int l, int r) {
tree[p].l = l, tree[p].r = r;
if (l == r) {
tree[p].s.insert(a[l]);
return;
}
int mid = (l + r) >> 1;
build(ls, l, mid);
build(rs, mid + 1, r);
}
void modify(int op, int p, int l, int r, int x) {
if (l <= tree[p].l && tree[p].r <= r) {
if (op == 0) tree[p].s.insert(x);
else tree[p].s.erase(tree[p].s.find(x));
return;
}
int mid = (tree[p].l + tree[p].r) >> 1;
if (l <= mid) modify(op, ls, l, r, x);
if (r > mid) modify(op, rs, l, r, x);
}
int query(int p, int x) {
if (tree[p].l == tree[p].r) {
if (tree[p].s.size() == 0) return 0;
return (*(--tree[p].s.end()));
}
int mid = (tree[p].l + tree[p].r) >> 1, res = 0;
if (tree[p].s.size()) res = (*(--tree[p].s.end()));
if (x <= mid) res = max(res, query(ls, x));
else res = max(res, query(rs, x));
return res;
}
signed main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
build(1, 1, n);
int m; cin >> m;
while (m--) {
int op, x;
cin >> op;
idx++;
if (op == 1) {
cin >> l[idx] >> r[idx] >> v[idx];
modify(0, 1, l[idx], r[idx], v[idx]);
}
if (op == 2) {
cin >> x;
// cout << "debug=" << l[x] << ' ' << r[x] << ' ' << v[x] << endl;
modify(1, 1, l[x], r[x], v[x]);
}
if (op == 3) {
cin >> x;
cout << query(1, x) << endl;
}
}
}