牛客网提高五练习

时间:2023-02-13 17:50:29

吉老师的题,出的很有水平(应该是T1,T2难度)

同余方程

题意

给出 \(l1, l2, r1, r2, m\) 询问 \(\displaystyle \sum_{i = l1}^{r1} \sum_{j = l2}^{r2} [m | i \oplus j]\)

分析

对于前 \(30\) 分,我们可以简单枚举

对于第二个点,我们已知一个数

显然对于 \(10 ^ {18}\) 的数据范围,我们只能考虑 \(O(1)\) 或者 \(O(logn)\) 的算法

由于如果需要枚举 \([l, r]\) 中的数,时间复杂度将会乘上 \(O(n)\),我们需要考虑直接进行计算

因为确定了 \(x\) ,不同 \(y\) 的值与 \(x\) 异或的值一定不一样,并且一定是紧密排布的

因此我们可以直接计算出这些数的开头以及结尾,还有 \(m\) 的倍数的个数

对于所有数据

我们考虑将这个问题转换为前缀问题

\(f(x, y)\)\(\displaystyle \sum_{i = 0}^{x} \sum_{j = 0}^{y} [m | i \oplus j]\)

那么我们可以将原问题转换为 \(f(r1, r2) - f(l1, r2) - f(r1, l2) + f(l1, l2)\)

\(f(x, y)\) 可以通过二进制分解后选取一部分做上一种情况来求解

代码

#include <cstdio>
#include <iostream>
using namespace std;

#define qword long long

qword l1, l2, r1, r2, p, ans = 0;
#define M 998244353LL

qword solve(qword l, qword r, qword p) {
    if (l % p) l = (l / p + 1) * p;
    if (r % p) r = (r / p) * p;
    if (l > r) return 0;
    return (r / p - l / p + 1) % M;
}
qword solve(qword a, qword b){
    qword res = 0;
    for (qword i = a; i > 0; i -= i & -i)
        for (qword j = b; j > 0; j -= j & -j){
            qword m = min(i & -i, j & -j);
            qword n = max(i & -i, j & -j);
            qword x = ((i - (i & -i)) ^ (j - (j & -j))) | (n - 1);
            qword y = x - n + 1;
            (res += m % M * solve(y, x, p) % M) %= M;
        }
    return res;
}

int main() {
    cin >> l1 >> r1 >> l2 >> r2 >> p;
    if (r1 <= 5000 && r2 <= 5000) {
        for (qword i = l1; i <= r1; ++ i)
            for (qword j = l2; j <= r2; ++ j) {
                qword res = i ^ j;
                if (res % p == 0) ans ++;
                if (ans > M) ans -= M;
            }
    }
    else ans = solve(++r1, ++r2) - solve(l1, r2) - solve(r1, l2) + solve(l1, l2);
    cout << (ans + M) % M << endl;
}

旅行

题意

每条边至少走一次,从 \(1\) 出发回到 \(1\)

解析

应为这是一个闭合回路,如果我们将重复走的边进行拆分,那这张图就是个欧拉回路

因此,我们要保证每个点的度都是偶数

考虑添加哪些边使得每个点的度是偶数

由于每加一条边,只会改变两个点的度,并且这个度是可以传递的 最短路 + dp? 时间暴了

发现由于边权全部是 \(2\) 的幂次,并且幂次高的一定比所有幂次小的的和大,所以我们只要选取了高幂次的,我们就可以通过选取低幂次来降低总的 \(ans\)

由此,我们可以发现我们最终选取的值全部都在这个图的 MST

那么原问题将转变成选取那些边,可以使得这棵树上所有的点的权值全是偶数

简单DFS即可

注意取模

代码

#include <cstdio>
#include <iostream>
using namespace std;

#define qword long long

const int maxn = 5e5 + 10;

int n, m, u[maxn], v[maxn], w[maxn], fa[maxn], d[maxn], ver[maxn << 1], head[maxn << 1], Next[maxn << 1];
long long mi[maxn], tot = 0, ans = 0, edge[maxn << 1];
bool vis[maxn];

#define CH ch = getchar()

const int q = 998244353;

template <class T> inline void read(T &x) {
    x = 0; int ch, f = 0;
    for (CH; ch < '0' || ch > '9'; CH) if (ch == '-') f = 1;
    for (; ch >= '0' && ch <= '9'; CH) x = (x << 1) + (x << 3) + (ch ^ 48);
    if (f) x = -x;
}

int find(int x) {
    return fa[x] == x ? x : fa[x] = find(fa[x]);
}

inline void addEdge(int x, int y, int z) {
    ver[++tot] = y, edge[tot] = z, Next[tot] = head[x], head[x] = tot;
}

int dfs(int x) {
    vis[x] = 1;
    for (int i = head[x]; i; i = Next[i]) {
        int y = ver[i], z = edge[i];
        if (vis[y]) continue;
        int isAdd = dfs(y);
        if (isAdd) d[y] ++, d[x] ++, ans += z, ans %= q;
    }
    if (d[x] & 1) return 1;
    else return 0;
}

int main() {
    read(n), read(m); mi[0] = 1;
    for (int i = 1; i <= m; ++ i) {
        read(u[i]), read(v[i]); fa[i] = i;
        d[u[i]] ++, d[v[i]] ++; mi[i] = (mi[i - 1] << 1) % q;
        ans += mi[i]; ans %= q;
    }
    for (int i = 1; i <= m; ++ i) {
        int uu = u[i], vv = v[i]; // ww -> 2 ^ i
        int x = find(uu), y = find(vv);
        if (x != y) addEdge(uu, vv, mi[i]), addEdge(vv, uu, mi[i]), fa[x] = y;
    }
    dfs(1);
    cout << ans << endl;
}

串串

题意

如题所述

解析

\(T\) 是从 \(S\) 中删除一些字符得到的,\(T\) 一共有 \(\binom{c+d}{c}\)

考虑逆过程,我们在 \(T\) 上面插入 \(a-c\)\(0\)\(b-d\)\(1\) 来得到 \(S\)

但是直接插入会计算重复,比如 \(T\)\(000\),插入一个 \(0\)\(4\) 种位置,但是得 到的都是相同的结果

为了避免计算重复,我们限制所有统计的插入方案,必须对应着 \(T\)\(S\) 中最 靠前的一次出现,比如在上面的例子中,我们只统计 \(0\) 插入到最末尾的情况。

显然在这样的要求下, \(0\) 只能插入到 \(1\) 前面或者末尾,\(1\) 只能插入到 \(0\) 前面或者末尾。因此在一共 \(c+d+1\) 个间隔中,除了末尾以外,其他间隔都只能插入一种数字

可以枚举末尾有几个 \(0\) 几个 \(1\),然后用插板法统计方案

代码

#include <cstdio>

#define qword long long
const int p = 1e9 + 7;
const int maxn = 2010;
int ans, C[maxn << 1][maxn], a, b, c, d;

int main() {
    scanf("%d %d %d %d",&a, &b, &c, &d);
    C[0][0] = 1;
    for (int i = 1; i <= a + b; ++ i){
        C[i][0] = 1;
        for (int j = 1; j <= i; ++ j)
            C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % p;
    }
    if(!c || !d){
        printf("%lld\n", C[a + b][a]);
        return 0;
    }
    for (int i = 0; i <= a - c; ++ i)
        for (int j = 0; j <= b - d; ++ j){
            qword ret = C[i + j][j];
            (ret *= C[c + d][d]) %= p;
            int cq0 = a - c - i, cq1 = b - d - j;
            (ret *= C[cq1 + c - 1][c - 1]) %= p;
            (ret *= C[cq0 + d - 1][d - 1]) %= p;
            (ans += ret ) %= p;
        }
    printf("%d", ans);
}