2020 CCPC Weihai
A. Golden Spirit (CF gym 102798 A)
题目大意
桥的一边有位老人,另一边也有位老人,各边的老人都需要过桥去对面度假并回来,但老人不能独自过桥,需要你的帮助。一次你只能扶一位老人过桥。过桥的时间为,老人度假的时间为。老人度假完可以留在原地等待你来接他。问你帮助这个老人去桥对面度假并回来所需要的最小时间。
解题思路
首先,我们可以采取全部扶老人过去后再全部扶老人回来,此时老人过桥花的一共的时间是不可缺少的。假设我们初始在桥的左边,来回n次后,桥两边的老人都去了对面度假,我们在左边,此时面临两种选择
- 跑去桥对面,等待第一个老人度完假(如果需要)后,开始扶老人回来。
- 在原地等待第一个老人度完假(如果需要)后,开始扶老人回来。
我们会发现,一旦第一位老人度完假,我们开始扶老人回来后,之后的过程不会再发生等待老人度完假的情况,因为各个老人的度假时间一致,只是开始时间差了个,而这个刚好过桥时度过了。
所以,最终答案就在这两种选择的用时取最小,即
神奇的代码
#include <bits/stdc++.h> using namespace std; typedef long long LL; template <typename T> void read(T &x) { int s = 0, c = getchar(); x = 0; while (isspace(c)) c = getchar(); if (c == 45) s = 1, c = getchar(); while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar(); if (s) x = -x; } template <typename T> void write(T x, char c = '\n') { int b[40], l = 0; if (x < 0) putchar(45), x = -x; while (x > 0) b[l++] = x % 10, x /= 10; if (!l) putchar(48); while (l) putchar(b[--l] | 48); putchar(c); } int main(){ int T; read(T); while(T--){ LL n, x, t; read(n); read(x); read(t); LL ans = 4 * n * t + min(max(x + t + t - 2 * n * t, 0ll), t + max(x + t - 2 * n * t - t, 0ll)); write(ans); } return 0; }
B. Labyrinth (CF gym 102798 B)
题目大意
给定一个的网格,网格上有一些障碍物,有组询问,每组询问给定起点和终点,求最短的曼哈顿距离。
解题思路
如果起点和终点形成的矩形范围内没有障碍物,其答案就是曼哈顿距离,而如果有障碍物,我们则需要判断最短的距离是否存在,每次BFS显然不现实。
注意到障碍物数量很少(仅42个),我们可以从中入手。如果矩形范围内有障碍物,根据曼哈顿距离的性质,那么最短距离对应的路径,一定存在一种会绕过障碍物的,即经过障碍物旁边的路径。
因此我们可以预处理平面的每个点到障碍物旁边的点的最短距离。这样,对于每次询问,判断起点终点所形成的矩形范围是否有障碍物,无则横纵坐标差绝对值,有则枚举经过障碍物周围的点,取个最小值即可。
神奇的代码
#include <bits/stdc++.h> using namespace std; typedef long long LL; template<class T> void read(T &x){ int s = 0, c = getchar(); x = 0; while(isspace(c)) c = getchar(); if (c == 45) s = 1, c = getchar(); while(isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar(); if (s) x = -x; } template<class T> void write(T x, char c = '\n'){ int b[40], l = 0; if (x < 0) putchar(45), x = -x; while(x > 0) b[l ++] = x % 10, x /= 10; if (!l) putchar(48); while(l) putchar(b[--l] | 48); putchar(c); } #define pos(x, y) ((((x) - 1) * m + (y))) const int N = 2e5 + 8; const int M = 43; const pair<int,int> d[4] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; int n, m, k, q; vector<pair<pair<int, int>, int>> hole; int dis[M * 4][N]; int ans; bool sign[N]; bool visit[N]; void BFS(int x, int y, int id){ for(int i = 1; i <= n; ++ i) for(int j = 1; j <= m; ++ j){ visit[pos(i, j)] = false; } queue<pair<int,int>> team; team.push({x, y}); visit[pos(x, y)] = true; dis[id][pos(x, y)] = 0; while(!team.empty()){ auto u = team.front(); team.pop(); for(auto dv : d){ auto v = make_pair(u.first + dv.first, u.second + dv.second); if (v.first < 1 || v.first > n) continue; if (v.second < 1 || v.second > m) continue; if (visit[pos(v.first, v.second)]) continue; if (sign[pos(v.first, v.second)]) continue; dis[id][pos(v.first, v.second)] = dis[id][pos(u.first, u.second)] + 1; team.push({v.first, v.second}); visit[pos(v.first, v.second)] = true; } } } bool check(int u, int v, int x, int y){ if (u > x) swap(u, x); if (v > y) swap(v, y); for(auto h : hole){ if (u <= h.first.first && h.first.first <= x && v <= h.first.second && h.first.second <= y) return false; } return true; } int solve(int u, int v, int x, int y){ int qwq = 998244353; for(auto h : hole){ if (dis[h.second * 4][pos(u, v)] != -1 && dis[h.second * 4][pos(x, y)] != -1){ qwq = min(qwq, dis[h.second * 4][pos(u, v)] + dis[h.second * 4][pos(x, y)]); } if (dis[h.second * 4 + 1][pos(u, v)] != -1 && dis[h.second * 4 + 1][pos(x, y)] != -1){ qwq = min(qwq, dis[h.second * 4 + 1][pos(u, v)] + dis[h.second * 4 + 1][pos(x, y)]); } if (dis[h.second * 4 + 2][pos(u, v)] != -1 && dis[h.second * 4 + 2][pos(x, y)] != -1){ qwq = min(qwq, dis[h.second * 4 + 2][pos(u, v)] + dis[h.second * 4 + 2][pos(x, y)]); } if (dis[h.second * 4 + 3][pos(u, v)] != -1 && dis[h.second * 4 + 3][pos(x, y)] != -1){ qwq = min(qwq, dis[h.second * 4 + 3][pos(u, v)] + dis[h.second * 4 + 3][pos(x, y)]); } } if (qwq == 998244353) qwq = -1; return qwq; } int main(void){ read(n); read(m); read(k); read(q); for(int u, v, i = 1; i <= k; ++ i){ read(u); read(v); sign[pos(u, v)] = true; hole.push_back(make_pair(make_pair(u, v), i - 1)); for(int x = 1; x <= n; ++ x) for(int y = 1; y <= m; ++ y){ dis[(i - 1) * 4][pos(x, y)] = -1; dis[(i - 1) * 4 + 1][pos(x, y)] = -1; dis[(i - 1) * 4 + 2][pos(x, y)] = -1; dis[(i - 1) * 4 + 3][pos(x, y)] = -1; } } for(auto i : hole){ if (i.first.first != 1 && !sign[pos(i.first.first - 1, i.first.second)]) BFS(i.first.first - 1, i.first.second, i.second * 4); if (i.first.first != n && !sign[pos(i.first.first + 1, i.first.second)]) BFS(i.first.first + 1, i.first.second, i.second * 4 + 1); if (i.first.second != 1 && !sign[pos(i.first.first, i.first.second - 1)]) BFS(i.first.first, i.first.second - 1, i.second * 4 + 2); if (i.first.second != m && !sign[pos(i.first.first, i.first.second + 1)]) BFS(i.first.first, i.first.second + 1, i.second * 4 + 3); } for(int u, v, x, y, i = 1; i <= q; ++ i){ read(u); read(v); read(x); read(y); if (check(u, v, x, y)) ans = abs(y - v) + abs(x - u); else ans = solve(u, v, x, y); write(ans); } return 0; }
C. Rencontre (CF gym 102798 C)
题目大意
给定一棵树,三个人可能出现的位置集合,现在三个人会到某地集合,某地满足,三人到达此地的距离和最小。求距离和的期望值。
解题思路
给定一棵树上的三个点,如何确定一个点,使得三个点到该点的距离和最小?
很显然,该点不止一个,但可以是这三个点的LCA。观察距离和,我们可以发现,距离和可以等价成俩俩点的距离的和的一半。
根据期望的定义,分子就是所有三元组的距离和,分母就是情况数,而三元组的距离和可拆成求三次俩俩点的距离和,
所以问题就转换成求两个点集各选一个点出来的距离的和,这个问题可以用树形DP解决。
值得注意的是,选定两个集合求出距离和,还要乘以另外一个集合的大小,才是它对答案的贡献。比赛时演了队友QwQ。
神奇的代码
#include <bits/stdc++.h> using namespace std; typedef long long LL; template<class T> void read(T &x){ int s = 0, c = getchar(); x = 0; while(isspace(c)) c = getchar(); if (c == 45) s = 1, c = getchar(); while(isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar(); if (s) x = -x; } template<class T> void write(T x, char c = '\n'){ int b[40], l = 0; if (x < 0) putchar(45), x = -x; while(x > 0) b[l ++] = x % 10, x /= 10; if (!l) putchar(48); while(l) putchar(b[--l] | 48); putchar(c); } const int N = 2e5 + 8; int n; int A[N][3]; LL cc[3]; vector<pair<int,int>> edge[N]; LL sum[N][3], cnt[N][3]; LL len; bool sign[N][3]; void DFS(int u, int fa){ if (sign[u][1]) ++ cnt[u][1]; if (sign[u][2]) ++ cnt[u][2]; for(auto v : edge[u]){ if (v.first == fa) continue; DFS(v.first, u); len += (sum[u][1] * cnt[v.first][2] + sum[v.first][2] * cnt[u][1] + v.second * cnt[v.first][2] * cnt[u][1]); len += (sum[u][2] * cnt[v.first][1] + sum[v.first][1] * cnt[u][2] + v.second * cnt[v.first][1] * cnt[u][2]); sum[u][1] += sum[v.first][1] + v.second * cnt[v.first][1]; sum[u][2] += sum[v.first][2] + v.second * cnt[v.first][2]; cnt[u][1] += cnt[v.first][1]; cnt[u][2] += cnt[v.first][2]; } } void solve(int x, int y){ for(int i = 1; i <= n; ++ i){ sign[i][0] = sign[i][1] = sign[i][2] = false; sum[i][0] = 0; sum[i][1] = 0; sum[i][2] = 0; cnt[i][0] = 0; cnt[i][1] = 0; cnt[i][2] = 0; } for(int i = 1; i <= cc[x]; ++ i){ sign[A[i][x]][1] = true; } for(int i = 1; i <= cc[y]; ++ i){ sign[A[i][y]][2] = true; } DFS(1, 1); } LL ans=0; int main(void){ read(n); for(int u, v, w, i = 1; i < n; ++ i){ read(u); read(v); read(w); edge[u].push_back({v, w}); edge[v].push_back({u, w}); } for(int i = 0; i < 3; ++ i){ read(cc[i]); for(int a, j = 1; j <= cc[i]; ++ j){ read(a); A[j][i] = a; } } long double fans=0.0; len=0; solve(0, 1); fans+=len * 1.0/(long double)(2ll*cc[0]*cc[1]); len=0; solve(0, 2); fans+=len * 1.0/(long double)(2ll*cc[0]*cc[2]); len=0; solve(1, 2); fans+=len * 1.0/(long double)(2ll*cc[2]*cc[1]); cout<<fixed<<setprecision(8)<<fans<<endl; return 0; }
D. ABC Conjecture (CF gym 102798 D)
题目大意
给定一个,问是否存在,使得且。其中
解题思路
上来直接打表可以发现如果c质因数分解后每个质数的指数都是1则不存在,否则存在。
由于可达,我们可以筛的质数,来判断c包含的这些质数的指数是否有大于1的,对于大于的质数,我们可以发现最多只会有两个,因为三个的会大于,所以此时我们只用判断它是否是一个数的平方即可。
神奇的代码
#include <bits/stdc++.h> using namespace std; typedef long long LL; template<class T> void read(T &x){ int s = 0, c = getchar(); x = 0; while(isspace(c)) c = getchar(); if (c == 45) s = 1, c = getchar(); while(isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar(); if (s) x = -x; } template<class T> void write(T x, char c = '\n'){ int b[40], l = 0; if (x < 0) putchar(45), x = -x; while(x > 0) b[l ++] = x % 10, x /= 10; if (!l) putchar(48); while(l) putchar(b[--l] | 48); putchar(c); } const int N = 1e6 + 8; vector<int> ans; bool vaild[N + 1]; void pre(){ memset(vaild, true, sizeof(vaild)); for(int i = 2; i <= N; ++ i){ if (vaild[i]){ ans.push_back(i); } for(auto j : ans){ if ((LL)i * j > N) break; vaild[i * j] = false; if (i % j == 0) break; } } } bool check(LL r){ LL x = (LL)sqrt(r); return x * x == r; } int main(void){ pre(); int t; read(t); for(int i = 1; i <= t; ++ i){ LL r; read(r); bool sign = 0; for(auto i : ans){ if (r % i == 0){ int cnt = 0; while(r % i == 0){ ++ cnt; r /= i; if (cnt > 1) sign = true; } } } if (sign) puts("yes"); else if (check(r) && r != 1) puts("yes"); else puts("no"); } return 0; }
E. So Many Possibilities... (CF gym 102798 E)
题目大意
给定只怪物的血量,你可以进行次进攻,每次进攻随机对一只剩余血量大于的怪物造成点伤害,问最后死亡(即血量为0)的怪物数的期望值。
解题思路
由于每次进攻是随机对一只剩余血量大于的怪物造成伤害,即每次的概率是不一样的,因此我们不能采取那种,单独算分子(各个情况死的怪物数的和)和分母(情况属)再相除。
根据定义,我们要算出怪物死亡数为的概率,然后答案就是,现在问题就是如何计算。
由于进攻一次的概率取决于活着的怪物数,我们可以设表示进攻次,怪物生死状态为的概率。
但转移的话我们会发现一个致命问题:由于没有记录怪物血量,我们不清楚进攻一次会不会造成一只怪物死亡。
即我们不清楚进行一次进攻之后,对哪些怪物造成伤害后,不变,而对哪些奄奄一息(血量为1)的怪物造成伤害后,发生变化。
值得注意的是,此处每次转移,打的怪物都是特定的怪物。
但结果我们发现并不可行,得修改状态。
先鸽了
神奇的代码
#include <bits/stdc++.h> using namespace std; typedef long long LL; template <typename T> void read(T &x) { int s = 0, c = getchar(); x = 0; while (isspace(c)) c = getchar(); if (c == 45) s = 1, c = getchar(); while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar(); if (s) x = -x; } template <typename T> void write(T x, char c = '\n') { int b[40], l = 0; if (x < 0) putchar(45), x = -x; while (x > 0) b[l++] = x % 10, x /= 10; if (!l) putchar(48); while (l) putchar(b[--l] | 48); putchar(c); } const int N = 15; const int M = 102; long double C[M][M], cnt[M][1 << (N)], tmp[N][M]; long double dp[M][1 << (N)]; long double ans; int n, m, hp[N], sum[1 << (N)], dead[1 << (N)]; void pre(int s){ for(int i = 0; i < n; ++ i){ if ((s >> i) & 1){ dead[s] += 1; sum[s] += hp[i]; } } } void DFS(int pos, int s, int dd){ if (pos == n){ for(int i = 0; i <= m; ++ i) cnt[i][s] = tmp[dd][i]; return; } for(int i = 0; i <= m; ++ i) for(int j = 0; j < hp[pos] && j <= i; ++ j) tmp[dd + 1][i] += tmp[dd][i - j] * C[i][j]; DFS(pos + 1, s, dd + 1); for(int i = 0; i <= m; ++ i) tmp[dd + 1][i] = 0; DFS(pos + 1, s | (1 << pos), dd); } int main(void){ read(n); read(m); C[0][0] = 1; for(int i = 1; i <= m; ++ i){ C[i][0] = 1; for(int j = 1; j <= i; ++ j){ C[i][j] = C[i - 1][j] + C[i - 1][j - 1]; } } int tot = 0; for(int i = 0; i < n; ++ i){ read(hp[i]); tot += hp[i]; } if (tot == m){ write(n); return 0; } for(int i = 0; i < (1 << n); ++ i){ pre(i); } tmp[0][0] = 1; DFS(0, 0, 0); dp[0][0] = 1; for(int i = 0; i < (1 << n); ++ i){ for(int j = sum[i]; j <= min(sum[(1 << n) - 1] - (n - dead[i]), m); ++ j){ for(int k = 0; k < n; ++ k){ if ((i >> k) & 1){ dp[j][i] += dp[j - 1][i ^ (1 << k)] * C[j - 1 - sum[i ^ (1 << k)]][hp[k] - 1] / (n - dead[i ^ (1 << k)]); } } if (j != sum[i]) dp[j][i] += dp[j - 1][i] / (n - dead[i]); } } ans = 0; for(int i = 0; i < (1 << n); ++ i){ if (sum[i] <= m) ans += dp[m][i] * cnt[m - sum[i]][i] * dead[i]; } cout<<fixed<<setprecision(10)<<ans<<endl; return 0; }
F. Skeleton Dynamization (CF gym 102798 F)
题目大意
解题思路
神奇的代码
G. Caesar Cipher (CF gym 102798 G)
题目大意
给定一个长度为的数组,有次操作,操作分两类,操作一是给指定的区间的所有数加一并模,操作二是询问两个区间(可以有交集)是否一样。
解题思路
操作二可以用Hash判断是否一样,但修改的复杂度较大。
我们可以将该数组看做是个进制的数,在十进制下即为,我们用线段树来维护区间和,操作一相当于是个等比数列相加,操作二相当于两个区间和的比较,其中一个乘以某个系数,使得进制位数一致。
但所有数会取模,一开始将可以不用考虑该情况,但结果双进制也救不了我们qwq,虽然这一定会冲突但冲突概率还是超大超大的。但我们注意到,如果我们暴力修正的话,即把变成,这种情况也只会发生次,加上线段树维护需要的也不会超,所以我们可以暴力修改,但要判断有数到达,我们还需要另一棵线段树来维护数组的最大值,此时可以为其他质数了。
代码是从改的,所以保留了双进制。
神奇的代码
#include <bits/stdc++.h> using namespace std; typedef long long LL; template<class T> void read(T &x){ int s = 0, c = getchar(); x = 0; while(isspace(c)) c = getchar(); if (c == 45) s = 1, c = getchar(); while(isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar(); if (s) x = -x; } template<class T> void write(T x, char c = '\n'){ int b[40], l = 0; if (x < 0) putchar(45), x = -x; while(x > 0) b[l ++] = x % 10, x /= 10; if (!l) putchar(48); while(l) putchar(b[--l] | 48); putchar(c); } const int N = 5e5 + 8; const int up = 65536; const LL mo = 998244353; const LL ji1 = 998; const LL ji2 = 233; LL base1[N]; LL base2[N]; LL sum1[N]; LL sum2[N]; LL a[N]; class Segment{ #define lson root << 1 #define rson root << 1 | 1 LL hash1[N * 4]; LL hash2[N * 4]; LL lazy[N * 4]; LL MAX[N * 4]; public: void pushdown(int root, int l, int r){ int mid = (l + r) >> 1; lazy[lson] += lazy[root]; hash1[lson] += (sum1[mid] - sum1[l - 1]) * lazy[root]; hash1[lson] %= mo; hash1[lson] += mo; hash1[lson] %= mo; hash2[lson] += (sum2[mid] - sum2[l - 1]) * lazy[root]; hash2[lson] %= mo; hash2[lson] += mo; hash2[lson] %= mo; MAX[lson] += lazy[root]; lazy[rson] += lazy[root]; hash1[rson] += (sum1[r] - sum1[mid]) * lazy[root]; hash1[rson] %= mo; hash1[rson] += mo; hash1[rson] %= mo; hash2[rson] += (sum2[r] - sum2[mid]) * lazy[root]; hash2[rson] %= mo; hash2[rson] += mo; hash2[rson] %= mo; MAX[rson] += lazy[root]; lazy[root] = 0; } void build(int root, int l, int r){ if (l == r){ hash1[root] = a[l] * base1[l] % mo; hash2[root] = a[l] * base2[l] % mo; MAX[root] = a[l]; lazy[root] = 0; return; } int mid = (l + r) >> 1; build(lson, l, mid); build(rson, mid + 1, r); hash1[root] = (hash1[rson] + hash1[lson]) % mo; hash2[root] = (hash2[rson] + hash2[lson]) % mo; lazy[root] = 0; MAX[root] = max(MAX[lson], MAX[rson]); } void update(int root, int l, int r, int ll, int rr){ if (ll <= l && r <= rr){ hash1[root] = hash1[root] + sum1[r] - sum1[l - 1]; hash1[root] %= mo; hash1[root] += mo; hash1[root] %= mo; hash2[root] = hash2[root] + sum2[r] - sum2[l - 1]; hash2[root] %= mo; hash2[root] += mo; hash2[root] %= mo; MAX[root] += 1; lazy[root] ++; return; } if (lazy[root]) pushdown(root, l, r); int mid = (l + r) >> 1; if (ll <= mid) update(lson, l, mid, ll, rr); if (rr > mid) update(rson, mid + 1, r, ll, rr); hash1[root] = (hash1[lson] + hash1[rson]) % mo; hash2[root] = (hash2[lson] + hash2[rson]) % mo; MAX[root] = max(MAX[lson], MAX[rson]); } pair<LL,LL> query(int root, int l, int r, int ll, int rr){ if (ll <= l && r <= rr){ return make_pair(hash1[root], hash2[root]); } if (lazy[root]) pushdown(root, l, r); int mid = (l + r) >> 1; pair<LL,LL> qwq = {0, 0}; pair<LL,LL> qaq = {0, 0}; if (ll <= mid) qwq = query(lson, l, mid, ll, rr); if (rr > mid) qaq = query(rson, mid + 1, r, ll, rr); qwq.first += qaq.first; qwq.first %= mo; qwq.first += mo; qwq.first %= mo; qwq.second += qaq.second; qwq.second %= mo; qwq.second += mo; qwq.second %= mo; return qwq; } void reflush(int root, int l, int r){ if (MAX[root] < up) return; if (l == r){ MAX[root] -= up; hash1[root] = MAX[root] * base1[l] % mo; hash2[root] = MAX[root] * base2[l] % mo; return; } if (lazy[root]) pushdown(root, l, r); int mid = (l + r) >> 1; if (MAX[lson] >= up){ reflush(lson, l, mid); } if (MAX[rson] >= up){ reflush(rson, mid + 1, r); } MAX[root] = max(MAX[lson], MAX[rson]); hash1[root] = (hash1[lson] + hash1[rson]) % mo; hash2[root] = (hash2[lson] + hash2[rson]) % mo; } }Seg; int q, n; int main(void){ read(n); read(q); base1[0] = 1; base2[0] = 1; sum1[0] = 1; sum2[0] = 1; for(int i = 1; i <= n; ++ i) read(a[i]); for(int i = 1; i <= n; ++ i) base1[i] = base1[i - 1] * ji1 % mo, sum1[i] = (sum1[i - 1] + base1[i]) % mo; for(int i = 1; i <= n; ++ i) base2[i] = base2[i - 1] * ji2 % mo, sum2[i] = (sum2[i - 1] + base2[i]) % mo; Seg.build(1, 1, n); for(int x, y, z, k, i = 1; i <= q; ++ i){ read(x); read(y); read(z); if (x == 1){ Seg.update(1, 1, n, y, z); Seg.reflush(1, 1, n); }else{ read(k); if (y > z) swap(y, z); auto tmp = Seg.query(1, 1, n, y, y + k - 1); auto qaq = (tmp.first * base1[z - y] + mo) % mo; auto qbq = (tmp.second * base2[z - y] + mo) % mo; auto tmoo = Seg.query(1, 1, n, z, z + k - 1); auto QaQ = (tmoo.first + mo) % mo; QaQ += mo; QaQ %= mo; auto QbQ = (tmoo.second + mo) % mo; QbQ += mo; QbQ %= mo; if (qaq == QaQ && qbq == QbQ) puts("yes"); else puts("no"); } } return 0; }
H. Message Bomb (CF gym 102798 H)
题目大意
给定一个事件顺序发生列表,包括,哪位学生加入哪个群聊,哪位学生退出哪个群聊,哪个学生在哪个群聊发送了一条消息。最后问每个学生各收到了多少条消息。
解题思路
信息的属性有两个,一个是发生的时间,一个是发生的地点(群聊),对于一个学生而言,他在一个组内收到的消息数即为,离开时该组的消息数-进来时该组的消息数。那么对于一个学生收到的消息数,他进某群聊时就减去该组的消息数,离开群聊时就加上该组的消息数即可。特别注意发消息的同学自己也会收到这个消息。
神奇的代码
#include <bits/stdc++.h> using namespace std; typedef long long LL; template<class T> void read(T &x){ int s = 0, c = getchar(); x = 0; while(isspace(c)) c = getchar(); if (c == 45) s = 1, c = getchar(); while(isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar(); if (s) x = -x; } template<class T> void write(T x, char c = '\n'){ int b[40], l = 0; if (x < 0) putchar(45), x = -x; while(x > 0) b[l ++] = x % 10, x /= 10; if (!l) putchar(48); while(l) putchar(b[--l] | 48); putchar(c); } const int N = 1e5 + 8; const int M = 2e5 + 8; int cnt[N]; set<int> g[N]; int n, m, s; int ans[M]; int main(){ read(n); read(m); read(s); while(s--){ int op, x, y; read(op); read(x); read(y); if (op == 1){ ans[x] -= cnt[y]; g[y].insert(x); }else if (op == 2){ ans[x] += cnt[y]; g[y].erase(x); }else{ ans[x] --; cnt[y] ++; } } for(int i = 1; i <= n; ++ i) for(auto x : g[i]) ans[x] += cnt[i]; for(int i = 1; i <= m; ++ i) write(ans[i]); return 0; }
I. Sean the Cuber (CF gym 102798 I)
题目大意
解题思路
神奇的代码
J. Steins;Game (CF gym 102798 J)
题目大意
给定堆未染色的石子,现在需要让他们染成黑白两种颜色中的一种。然后开始取石子游戏。
一次操作,可以从白堆中的任意一堆取任意个,或者从黑堆中石子数最少的堆中取任意个。
问使后手必胜的涂色方案数。
解题思路
这道题让我明白了所谓独立局面。
对于白堆,就是普通的游戏,各个堆之间相互独立,所以堆白堆的值就是它们个数的异或。
剩下的黑堆石头,因为每次操作只能对黑堆中数量最少的那堆操作,所以这堆黑色石头是一个局面,它自有一个值。
所以最终局面的值就是所有白堆的个值和黑堆的个值的异或,值为0则后手必胜。
至于黑堆的值,通过打表或者简单的枚举求值即可得到其值为
其中是黑堆中石子数等于最小石子数的堆数,是黑堆中石子数的最小值。
接下来考虑如何求方案数,显然枚举涂色方案不现实,我们要换个方向考虑。
注意到影响黑堆的值的因素有三个,我们可以从这入手,枚举黑堆的最小石子数,最小石子数的堆数,以及所有黑堆是否有相同石子数。
我们将堆按照石子数从小到大排序,然后枚举黑堆的最小石子数,那么石子数小于的堆都涂色白色,然后考虑大于该堆的数量。
对于它们,由于怎么涂色,对黑堆的值不会有影响。
因此,当我们枚举了最小黑堆的数量以及是否黑堆石子数都相等后,得到了一个值为前面白堆的值异或黑堆的值,
剩下要求的就是石子数大于的堆中,任选若干堆,它们数量异或和为的方案数,
而这可以用线性基维护。具体来说,将石子数大于的堆的石子数构造一组线性基,如果能表示,那么方案数就是,其中是石子数大于的堆数,是线性基的数量,因为能表示,无论我怎么选择堆,我都能通过组基向量表示出。
所以我们从大到小枚举,枚举的数量,枚举全部黑堆是否相等,计算方案数。
值得注意的是,在枚举全部黑堆并非相等时,如果为零,在计算时要减去一种情况即石子数大于的堆全部涂成白色的情况,因为此时全部黑堆石子数相等,并不是我们枚举的情况。
神奇的代码
#include <bits/stdc++.h> using namespace std; typedef long long LL; template <typename T> void read(T &x) { int s = 0, c = getchar(); x = 0; while (isspace(c)) c = getchar(); if (c == 45) s = 1, c = getchar(); while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar(); if (s) x = -x; } template <typename T> void write(T x, char c = '\n') { int b[40], l = 0; if (x < 0) putchar(45), x = -x; while (x > 0) b[l++] = x % 10, x /= 10; if (!l) putchar(48); while (l) putchar(b[--l] | 48); putchar(c); } const int N = 63; const int M = 1e5 + 8; const LL mo = 1e9 + 7; int n, ji_num; LL ji[N + 1]; LL jie[M], invjie[M]; LL ans, suml, sumr, sum; map<LL, LL, greater<LL>> qwq; LL qpower(LL a, LL b){ LL qwq = 1; while(b){ if (b & 1) qwq = qwq * a % mo; a = a * a % mo; b >>= 1; } return qwq; } LL inv(LL x){ return qpower(x, mo - 2); } LL val(LL M, int cnt, int sign){ return (M - ((cnt + sign) & 1)); } bool check(LL x){ for(int i = N; i >= 0; -- i){ if ((x >> i) & 1) x ^= ji[i]; } return x == 0; } void insert(LL x){ for(int i = N; i >= 0; -- i){ if ((x >> i) & 1){ if (!ji[i]){ ji[i] = x; ++ ji_num; break; } else{ x ^= ji[i]; } } } } LL C(int n, int m){ if (n < m) return 0; return jie[n] * invjie[m] % mo * invjie[n - m] % mo; } int main(void){ read(n); jie[0] = invjie[0] = 1; sumr = suml = sum = 0; LL a; for(int i = 1; i <= n; ++ i){ read(a); ++ qwq[a]; sumr ^= a; jie[i] = jie[i - 1] * i % mo; invjie[i] = inv(jie[i]); } LL sg = 0; ans = (sumr == 0); for(auto i : qwq){ if (i.second & 1) sumr ^= i.first; for(int j = 1; j <= i.second; ++ j){ sg = val(i.first, j, 1) ^ sumr ^ suml; if ((i.second - j) & 1) sg ^= i.first; if (sg == 0){ ans += C(i.second, j); ans %= mo; } if (sum == 0) continue; sg = val(i.first, j, 0) ^ sumr; if ((i.second - j) & 1) sg ^= i.first; if (check(sg)){ ans += (qpower(2, sum - ji_num) - ((sg ^ suml) == 0)) * C(i.second, j) % mo; ans %= mo; } } insert(i.first); if (i.second & 1) suml ^= i.first; sum += i.second; } write(ans); return 0; }
K. Tree Tweaking (CF gym 102798 K)
题目大意
解题思路
神奇的代码
L. Clock Master (CF gym 102798 L)
题目大意
给定一个数,将拆成一个向量,其中,使得的不同向量个数最多。求这个数量。
解题思路
容易发现,时,各个向量都是不同的,且不同的数量就这么多,因为时是零向量,亦是零向量,期间不可能会出现零向量。且也不会有相同的,因为有相同的话就说明出现循环节,而这循环节不会再到零向量了。
因此,问题就转换成如何决定这些使得其最小公倍数最大。很显然,我们只用考虑为质数和质数的幂的情况,它们之间的乘积就是最小公倍数。所以我们的问题就是考虑一个质数要不要选,要选的话应该选几次方,而这就是一个分组背包DP。
神奇的代码
#include <bits/stdc++.h> using namespace std; typedef long long LL; template<class T> void read(T &x){ int s = 0, c = getchar(); x = 0; while(isspace(c)) c = getchar(); if (c == 45) s = 1, c = getchar(); while(isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar(); if (s) x = -x; } template<class T> void write(T x, char c = '\n'){ int b[40], l = 0; if (x < 0) putchar(45), x = -x; while(x > 0) b[l ++] = x % 10, x /= 10; if (!l) putchar(48); while(l) putchar(b[--l] | 48); putchar(c); } const int N = 3500; const int M = 3e4 + 8; vector<int> prime; vector<vector<pair<int,long double>>> g; bool visit[M]; long double Ln[M], dp[M]; int t, b; void pre(){ for(int i = 2; i < M; ++ i) Ln[i] = log(i); for(int i = 2; i < M; ++ i){ if (!visit[i]) prime.push_back(i); for(auto j : prime){ if (j * i >= M) break; visit[j * i] = true; if (i % j == 0) break; } } } int main(){ pre(); g.resize(prime.size()); for(size_t i = 0; i < prime.size(); ++ i){ for(int j = prime[i]; j < M; j *= prime[i]) g[i].push_back({j, Ln[j]}); } for(int i = 1; i < M; ++ i) dp[i] = -1; dp[0] = 0; for(size_t i = 0; i < prime.size(); ++ i){ for(int k = M - 8; k >= 0; -- k){ for(auto j : g[i]) if (k - j.first >= 0 && dp[k - j.first] != -1) dp[k] = max(dp[k], dp[k - j.first] + j.second); } } for(int i = 1; i < M; ++ i) dp[i] = max(dp[i], dp[i - 1]); read(t); while(t--){ read(b); cout<<fixed<<setprecision(8)<<dp[b]<<endl; } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
2019-11-28 Educational Codeforces Round 77 (Rated for Div. 2)