2020 CCPC Weihai

A. Golden Spirit (CF gym 102798 A)

题目大意

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

解题思路

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

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

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

所以,最终答案就在这两种选择的用时取最小,即(4nt + min left( max left( 2t + x - 2nt, 0 ight) , max left( t + x - 2nt - t, 0 ight) ight))

神奇的代码
#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 = '
') {
    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 imes 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 = '
'){
    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 = '
'){
    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),问是否存在(a)(b)使得(a + b < c)(rad(abc) < c)。其中(rad(n) = prodlimits _{p|n & p in Prime} p)

解题思路

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

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

神奇的代码
#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 = '
'){
    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)),然后答案就是(sumlimits_{i = 0} ^ {n} k imes pleft(k ight)),现在问题就是如何计算(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 = '
') {
    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)进制的数,在十进制下即为(a_0 imes b^{0} + a_{1} imes b^{1} + dots + a_{n - 1} imes b^{n - 1}),我们用线段树来维护区间和,操作一相当于是个等比数列相加,操作二相当于两个区间和的比较,其中一个乘以某个系数,使得进制位数一致。

但所有数会取模,一开始将(b=65536)可以不用考虑该情况,但结果双进制也救不了我们qwq,虽然这一定会冲突但冲突概率还是超大超大的。但我们注意到,如果我们暴力修正的话,即把(65536)变成(0),这种情况也只会发生(dfrac{nq}{65536} = 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 = '
'){
    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 = '
'){
    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)值就是它们个数的异或。

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

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

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

[m - ((c_m - [ ext{所有黑堆有相同石子数}]) mod 2) ]

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

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

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

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

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

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

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

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

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

值得注意的是,在枚举全部黑堆并非相等时,如果(v)为零,在计算(2^{m-k})时要减去一种情况即石子数大于(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 = '
') {
    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)拆成一个向量((t_1, t_2, ..., t_m)),其中(t_1 + t_2 + ... + t_m leq b),使得((k mod t_1, k mod t_2, k mod t_3, ... , k mod t_m), k = 0, 1, 2, 3, ...)的不同向量个数最多。求这个数量。

解题思路

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

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


原文地址:https://www.cnblogs.com/Lanly/p/14051200.html