2020 CCPC Weihai

A. Golden Spirit (CF gym 102798 A)

题目大意

桥的一边有n位老人,另一边也有n位老人,各边的老人都需要过桥去对面度假并回来,但老人不能独自过桥,需要你的帮助。一次你只能扶一位老人过桥。过桥的时间为t,老人度假的时间为x。老人度假完可以留在原地等待你来接他。问你帮助这2n个老人去桥对面度假并回来所需要的最小时间。

解题思路

首先,我们可以采取全部扶老人过去后再全部扶老人回来,此时老人过桥花的一共2×2n×t的时间是不可缺少的。假设我们初始在桥的左边,来回n次后,桥两边的老人都去了对面度假,我们在左边,此时面临两种选择

  • 跑去桥对面,等待第一个老人度完假(如果需要)后,开始扶老人回来。
  • 在原地等待第一个老人度完假(如果需要)后,开始扶老人回来。

我们会发现,一旦第一位老人度完假,我们开始扶老人回来后,之后的过程不会再发生等待老人度完假的情况,因为各个老人的度假时间一致,只是开始时间差了个t,而这个t刚好过桥时度过了。

所以,最终答案就在这两种选择的用时取最小,即4nt+min(max(2t+x2nt,0),max(t+x2ntt,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);
}
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)

题目大意

给定一个n×m的网格,网格上有一些障碍物,有q组询问,每组询问给定起点和终点,求最短的曼哈顿距离。

解题思路

如果起点和终点形成的矩形范围内没有障碍物,其答案就是曼哈顿距离,而如果有障碍物,我们则需要判断最短的距离是否存在,每次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,问是否存在ab使得a+b<crad(abc)<c。其中rad(n)=p|n&pPrimep

解题思路

上来直接打表可以发现如果c质因数分解后每个质数的指数都是1则不存在,否则存在。

由于c可达1018,我们可以筛106的质数,来判断c包含的这些质数的指数是否有大于1的,对于大于106的质数,我们可以发现最多只会有两个,因为三个的会大于1018,所以此时我们只用判断它是否是一个数的平方即可。

神奇的代码
#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)

题目大意

给定n只怪物的血量,你可以进行m次进攻,每次进攻随机对一只剩余血量大于0的怪物造成1点伤害,问最后死亡(即血量为0)的怪物数的期望值。

解题思路

由于每次进攻是随机对一只剩余血量大于0的怪物造成伤害,即每次的概率是不一样的,因此我们不能采取那种,单独算分子(各个情况死的怪物数的和)和分母(情况属)再相除。

根据定义,我们要算出怪物死亡数为k的概率p(k),然后答案就是i=0nk×p(k),现在问题就是如何计算p(k)

由于进攻一次的概率取决于活着的怪物数,我们可以设dp[i][s]表示进攻i次,怪物生死状态为s的概率。

但转移的话我们会发现一个致命问题:由于没有记录怪物血量,我们不清楚进攻一次会不会造成一只怪物死亡。

即我们不清楚进行一次进攻之后,对哪些怪物造成伤害后,s不变,而对哪些奄奄一息(血量为1)的怪物造成伤害后,s发生变化。

值得注意的是,此处每次转移,打的怪物都是特定的怪物。

但结果我们发现并不可行,得修改状态。

先鸽了

神奇的代码
#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)

题目大意

给定一个长度为n的数组a,有q次操作,操作分两类,操作一是给指定的区间的所有数加一并模65536,操作二是询问两个区间(可以有交集)是否一样。

解题思路

操作二可以用Hash判断是否一样,但修改的复杂度较大。

我们可以将该数组看做是个b进制的数,在十进制下即为a0×b0+a1×b1++an1×bn1,我们用线段树来维护区间和,操作一相当于是个等比数列相加,操作二相当于两个区间和的比较,其中一个乘以某个系数,使得进制位数一致。

但所有数会取模,一开始将b=65536可以不用考虑该情况,但结果双进制也救不了我们qwq,虽然这一定会冲突但冲突概率还是超大超大的。但我们注意到,如果我们暴力修正的话,即把65536变成0,这种情况也只会发生nq65536=4e6次,加上线段树维护需要的logn也不会超,所以我们可以暴力修改,但要判断有数到达65536,我们还需要另一棵线段树来维护数组a的最大值,此时b可以为其他质数了。

代码是从b=65536改的,所以保留了双进制。

神奇的代码
#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)

题目大意

给定n堆未染色的石子,现在需要让他们染成黑白两种颜色中的一种。然后开始取石子游戏。

一次操作,可以从白堆中的任意一堆取任意个,或者从黑堆中石子数最少的堆中取任意个。

问使后手必胜的涂色方案数。

解题思路

这道题让我明白了所谓独立局面。

对于白堆,就是普通的Nim游戏,各个堆之间相互独立,所以k堆白堆的Sg值就是它们个数的异或。

剩下的nk黑堆石头,因为每次操作只能对黑堆中数量最少的那堆操作,所以这nk堆黑色石头是一个局面,它自有一个Sg值。

所以最终局面的Sg值就是所有白堆的kSg值和黑堆的1Sg值的异或,值为0则后手必胜。

至于黑堆的Sg值,通过打表或者简单的枚举求值即可得到其值为

m((cm[所有黑堆有相同石子数]) mod 2)

其中m是黑堆中石子数等于最小石子数的堆数,cm是黑堆中石子数的最小值。

接下来考虑如何求方案数,显然枚举涂色方案不现实,我们要换个方向考虑。

注意到影响黑堆的Sg值的因素有三个,我们可以从这入手,枚举黑堆的最小石子数,最小石子数的堆数,以及所有黑堆是否有相同石子数。

我们将堆按照石子数从小到大排序,然后枚举黑堆的最小石子数b,那么石子数小于b的堆W都涂色白色,然后考虑大于该堆的数量。

对于它们,由于怎么涂色,对黑堆的Sg值不会有影响。

因此,当我们枚举了最小黑堆的数量b以及是否黑堆石子数都相等后,得到了一个Sgv为前面白堆WSg值异或黑堆的Sg值,

剩下要求的就是石子数大于b的堆中,任选若干堆,它们数量异或和为v的方案数,

而这可以用线性基维护。具体来说,将石子数大于b的堆的石子数构造一组线性基,如果能表示v,那么方案数就是2mk,其中m是石子数大于b的堆数,k是线性基的数量,因为能表示,无论我怎么选择堆,我都能通过k组基向量表示出v

所以我们从大到小枚举b,枚举b的数量,枚举全部黑堆是否相等,计算方案数。

值得注意的是,在枚举全部黑堆并非相等时,如果v为零,在计算2mk时要减去一种情况即石子数大于b的堆全部涂成白色的情况,因为此时全部黑堆石子数相等,并不是我们枚举的情况。

神奇的代码
#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)

题目大意

给定一个数b,将b拆成一个向量(t1,t2,...,tm),其中t1+t2+...+tmb,使得(k mod t1, k mod t2, k mod t3, ... , k mod tm),k=0,1,2,3,...的不同向量个数最多。求这个数量。

解题思路

容易发现,k=0,1,2,...,lcm(t1,t2,t3,...,tm)1时,各个向量都是不同的,且不同的数量就这么多,因为k=0时是零向量,k=lcm(t1,t2,t3,...,tm)亦是零向量,期间不可能会出现零向量。且也不会有相同的,因为有相同的话就说明出现循环节,而这循环节不会再到零向量了。

因此,问题就转换成如何决定这些ti使得其最小公倍数最大。很显然,我们只用考虑ti为质数和质数的幂的情况,它们之间的乘积就是最小公倍数。所以我们的问题就是考虑一个质数要不要选,要选的话应该选几次方,而这就是一个分组背包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;
}


posted @   ~Lanly~  阅读(363)  评论(0编辑  收藏  举报
编辑推荐:
· 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)
点击右上角即可分享
微信分享提示