zoukankan      html  css  js  c++  java
  • 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;
    }
    


  • 相关阅读:
    OLEDB 数据变更通知
    Python处理正则表达式超时的办法
    OLEDB 静态绑定和数据转化接口
    Keepalived+LVS(DR)+MySQL
    Keepalived+Nginx实现Nginx的高可用
    ISCSI存储
    memcached随笔练习
    Varnish快速安装及测试
    Oracle及其相关软件历史版本下载地址
    使用VMwaver 克隆CentOS 6.9网卡配置报错
  • 原文地址:https://www.cnblogs.com/Lanly/p/14051200.html
Copyright © 2011-2022 走看看