题题题题题解解解解解

题题题题题面面面面面

A

这题的 \(w\) 太鬼畜了, 我 *龙门粗口* 11:57 发现题面改成了好做不少的样子!!!

字符串好题

因为状态比较少, 有上 AC自动机 的, 有上 伪AC自动机的, 还有上 哈希 的, 绝了

显然题意可以转化为不存在一个位置使得以这个为结尾的后缀和中同时存在 \(r, q + r, p + q + r\) 三个数

其实状压 \(DP\) 也不难想, 首先正难则反, 我们可以 \(DP\) 找不合法的数量

\(dp_{i, s}\) 表示以第 \(i\) 个位置结束的后缀集合为 \(s\)时不合法的数量, 因为我们关心的数最大是 \(p + q + r\), 所以只用存小于等于它的状态, \(r, q + r, p + q + r\),, 组合出 \(p, q, r\) 的任意一个数都不能大于 \(\max\{p, q, r\}\), 所以转移时新添的数只用枚举到 \(\max\{p, q, r\}\), 经过这些优化就可以过了

另外, 在写这个 \(DP\) 的时候会发现不容斥直接统计答案也可以, 因为不转移时就是合法的位置, 直接累加这里就行

正难则反
#include <cstdio>
#include <algorithm>
typedef long long ll;
const int maxn = 52, mod = 998244353;
int n, m, mx, a, b, c;
ll dp[maxn][1 << 20];
ll pow(ll a, int b){ ll ret = 1; while(b){ if(b & 1) ret = ret * a % mod; a = a * a % mod; b >>= 1; } return ret; }
int main(){
    #ifndef La_Pluma
        freopen("a.in", "r", stdin);
        freopen("a.out", "w", stdout);
    #endif
    scanf("%d%d%d%d%d", &n, &m, &a, &b, &c);
    mx = std::min(m, std::max({a, b, c}));
    b += c, a += b;
    int S = 1 << a;
    ll ans = pow(m, n);
    dp[0][0] = 1;
    #define to (((s << j) | (1 << j - 1)) & (S - 1))
    for(int i = 0; i < n; ++i){
        for(int s = 0; s < S; ++s){
            for(int j = 1; j <= mx; ++j){
                if((to & 1 << a - 1) && (to & 1 << b - 1) && (to & 1 << c - 1)) continue;
                if((dp[i + 1][to] += dp[i][s]) >= mod) dp[i + 1][to] -= mod;
            }
            dp[i + 1][0] = (dp[i + 1][0] + dp[i][s] * std::max(0, m - mx)) % mod;
        }
    }
    #undef to
    for(int s = 0; s < S; ++s) ans = (ans - dp[n][s] + mod) % mod;
    printf("%lld\n", ans);
    return 0;
}

正也不难则不反
#include <cstdio>
#include <algorithm>
typedef long long ll;
const int maxn = 52, mod = 998244353;
int n, m, mx, a, b, c;
ll dp[maxn][1 << 20], pw[maxn];
int main(){
    #ifndef La_Pluma
        freopen("a.in", "r", stdin);
        freopen("a.out", "w", stdout);
    #endif
    scanf("%d%d%d%d%d", &n, &m, &a, &b, &c);
    mx = std::min(m, std::max({a, b, c}));
    b += c, a += b;
    int S = 1 << a;
    ll ans = 0;
    dp[0][0] = 1;
    pw[0] = 1; for(int i = 1; i <= n; ++i) pw[i] = pw[i - 1] * m % mod;
    #define to (((s << j) | (1 << j - 1)) & (S - 1))
    for(int i = 0; i < n; ++i){
        for(int s = 0; s < S; ++s){
            for(int j = 1; j <= mx; ++j){
                if((to & 1 << a - 1) && (to & 1 << b - 1) && (to & 1 << c - 1))
                    ans = (ans + dp[i][s] * pw[n - i - 1]) % mod;
                else if((dp[i + 1][to] += dp[i][s]) >= mod) dp[i + 1][to] -= mod;
            }
            dp[i + 1][0] = (dp[i + 1][0] + dp[i][s] * std::max(0, m - mx)) % mod;
        }
    }
    #undef to
    printf("%lld\n", ans);

    return 0;
}

B

没有前导零, 直接钦定第一位不为 \(0\) 就好, 第一位一共有 \(b - 1\) 种情况, 其他 \(n - 1\) 位有 \(b\) 种情况, 所以答案就是 \((b - 1) \times b^{n - 1}\), 但是指数很大, 所以可以用扩欧对指数取模

主要说一下这道题的一些逸闻趣事:

  1. 我考场上打开了 \(T1\) 的输出文件 (观察可以发现, 四道题的输出都是一个整数) , 怎么都不对, 我还拿计算器算, 结果和我一样, 我一度以为是式子错了, 但是式子正确的非常显然, 我又想是不是文件开错了, (瞟了一眼), 是第一个每错哇(确实是 \(T1\) 的第一个)。。。
  2. 考场上, 闫丽人只知道欧拉定理, 也就是互质的情况, 所以融合了 crt, exLucas, 欧拉定理, exgcd 写出了一份十分诡异的代码
  3. 赛时有人忘却了数据范围, 对拍 \(\sqrt n\)\(\varphi\), 交的是 \(O(n)\) 筛的 \(\varphi\), 有人记得数据范围但是忘了根号求法, 硬上了线性筛, 这是低能者的做法, 这里点名表扬 \(WR\), 他的代码中如是写道

我忘了怎么根号筛phi了
不如复习一遍杜教筛吧

  1. 我们说回闫丽人, \(std\) 打挂了, \(2^2 \mod 2 = 1\), 闫丽人直接交普通的欧拉定理可以 \(AC\), 交残缺的扩展欧拉定理(不考虑指数小于 \(\varphi\) 的情况) \(70pts\), 完整扩欧 \(95pts\)(大概?)
  2. 由于标程中出现了不属于这个世界的禁忌知识, 大慈树王修改了标程, 在我的精神崩溃后的一瞬间
所以为啥还会有人忘记怎么求欧拉函数和扩欧呀, 考场上打错扩欧的我如是说到
#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
typedef long long ll;
const int maxn = 1e6 + 6;
ll p1, p2;
std::vector<int> prime;
int not_prime[maxn];
void get(int n){
    int sq = sqrt(n);
    for(int i = 2; i <= sq; ++i){
        if(!not_prime[i]) prime.push_back(not_prime[i] = i);
        for(auto j : prime){
            ll t = i * j;
            if(t > sq) break;
            not_prime[t] = j;
            if(j == not_prime[i]) break;
        }
    }
}
ll pow(ll a, ll b){
    ll ret = 1;
    while(b){
        if(b & 1) ret = ret * a % p1;
        a = a * a % p1;
        b >>= 1;
    }
    return ret;
}
char n[maxn], m[maxn];
int main(){
    #ifndef La_Pluma
        freopen("b.in", "r", stdin);
        freopen("b.out", "w", stdout);
    #endif
    scanf("%s%s%lld", n, m, &p1); 
    get(p1);
    int t = p2 = p1;
    for(auto i : prime){
        if(t % i == 0){
            p2 = p2 / i * (i - 1);
            while(t % i == 0) t /= i; 
        }
    }
    if(t != 1) p2 = p2 / t * (t - 1);
    int a = strlen(n), b = strlen(m);
    ll nn = 0, mm = 0;
    int flag = 0;
    for(int i = 0; i < a; ++i){
        if(nn * 10 + n[i] - 48 > p2) flag = 1;
        nn = (nn * 10 + n[i] - 48) % p2;
    }
    for(int i = 0; i < b; ++i) mm = (mm * 10 + m[i] - 48) % p1;
    if(--nn <= 0) nn += p2;
    if(mm <= 0) ++mm;
    if(flag) nn += p2;
    printf("%lld\n", pow(mm, nn) * (mm - 1) % p1);

    return 0;
}

C

净整些花里胡哨的, 为了得小数据的分, 我选择 \(bfs\), 为了得到树形结构的分, 我选择处理边权的前缀和, 找最优分叉点

行了, 题解写完了, 如果这个算法没有被拆成两半的话。。。

对这三个点分别跑一次 \(bfs\) 处理出这个点到每一个点的最短边数, 然后枚举分叉点, 分叉点到 \(v\) 的路要走两次, 到 \(w\)\(u\) 的路走一次, 选最小的几个边走 \(v\), 剩下的取最小走剩下的就行了

可是一个点到这三个点的 \(dis\) 和可以大于 \(m\)! 什么 *龙门粗口*

我可以不写这一行吗
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
typedef long long ll;
const int maxn = 2e5 + 5;
int n, m, u, v1, v2; ll ans = 1e18;
std::vector<int> edge[maxn]; ll val[maxn];
int que[maxn], vis[3][maxn], hd, tl;
void bfs(int u, int d){
    hd = tl = 0; 
    que[++tl] = u; vis[d][u] = 0;
    while(hd < tl){
        int u = que[++hd];
        for(auto v : edge[u]) if(vis[d][v] == -1){
            vis[d][v] = vis[d][u] + 1;
            que[++tl] = v;
        }
    }
}
int main(){
    #ifndef La_Pluma
        freopen("c.in", "r", stdin);
        freopen("c.out", "w", stdout);
    #endif
    scanf("%d%d%d%d%d", &n, &m, &v1, &u, &v2);
    memset(vis, -1, sizeof(vis));
    for(int i = 1; i <= m; ++i){
        int u, v;
        scanf("%d%d%lld", &u, &v, val + i);
        edge[u].push_back(v);
        edge[v].push_back(u);
    }
    std::sort(val + 1, val + m + 1);
    bfs(u, 0), bfs(v1, 1), bfs(v2, 2);
    for(int i = 2; i <= n; ++i) val[i] += val[i - 1];
    for(int i = 1; i <= n; ++i){
        if(vis[0][i] + vis[1][i] + vis[2][i] > m){ fprintf(stderr, "RNM, TQ"); continue;}
        ans = std::min(ans, val[vis[0][i]] + val[vis[0][i] + vis[1][i] + vis[2][i]]);
    }
    printf("%lld\n", ans);
    return 0;
}

D

我是 *龙门粗口*

用欧拉路考虑就行了, 对于一个有 \(0\) 个 或 \(2\) 个奇度数的点的图是存在欧拉路的, 也就是说可以一笔画的, 所以设 \(cnt\) 表示一个联通块中奇度数点的数量, 每一笔可以消除两个奇度数的影响, 一共需要 \(\dfrac{cnt}{2} - 1\) 次画满这个联通块(减一是因为这个联通块第一次画的时候不算抬笔了, 由于可能根本就没有奇点, 严格来说需要对 \(0\) 做一个 \(\max\)

\(m\) 是干什么的

看个乐呵的

\(m\) 是偶数时, 显然每一个点都不是奇点, 因为每条线段的贡献都是偶数, 直接统计联通块个数即可

\(m\) 是奇数时, 每条线段的贡献都是奇数数, 和 \(m = 1\) 时等价

$ \color{black}s$$\color{red}{andom}$ 发现异常

对于一个有 \(0\) 个 或 \(2\) 个奇度数的点的图是存在欧拉路的

那当一个图有奇数个奇点怎么办啊

一条边贡献总和为 \(2\) 的度, 整张图度的和不是偶数?

$STL$ 天下第一!!! 因为 $T$ 了所以删了 $map$ `T_T||`
#include <map>
#include <cstdio>
#include <vector>
#include <algorithm>
const int maxn = 4e3 + 3;
int n, m;
int a[maxn], b[maxn], c[maxn], d[maxn];
std::vector<int> edge[maxn * maxn], lsh1, lsh2;
int mp[maxn][maxn]; int cnt;
inline int id(int x, int y){ return mp[x][y] ? mp[x][y] : (mp[x][y] = ++cnt);}
int fa[maxn * maxn], ans[maxn * maxn];
int find(int x){ return fa[x] == x ? x : fa[x] = find(fa[x]);}
int main(){
    #ifndef La_Pluma
        freopen("d.in", "r", stdin);
        freopen("d.out", "w", stdout);
    #endif
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; ++i){
        scanf("%d%d%d%d", a + i, b + i, c + i, d + i);
        if(a[i] > c[i]) std::swap(a[i], c[i]);
        if(b[i] > d[i]) std::swap(b[i], d[i]);
        lsh1.push_back(a[i]), lsh2.push_back(b[i]);
        lsh1.push_back(c[i]), lsh2.push_back(d[i]);
    }
    for(int i = 1; i <= n * n; ++i) fa[i] = i;
    std::sort(lsh1.begin(), lsh1.end());
    std::sort(lsh2.begin(), lsh2.end());
    lsh1.resize(std::unique(lsh1.begin(), lsh1.end()) - lsh1.begin());
    lsh2.resize(std::unique(lsh2.begin(), lsh2.end()) - lsh2.begin());
    for(int i = 1; i <= n; ++i){
        a[i] = std::lower_bound(lsh1.begin(), lsh1.end(), a[i]) - lsh1.begin();
        b[i] = std::lower_bound(lsh2.begin(), lsh2.end(), b[i]) - lsh2.begin();
        c[i] = std::lower_bound(lsh1.begin(), lsh1.end(), c[i]) - lsh1.begin();
        d[i] = std::lower_bound(lsh2.begin(), lsh2.end(), d[i]) - lsh2.begin();
        int x = a[i], y = b[i];
        for(int x = a[i] + 1; x <= c[i]; ++x){
            fa[find(id(x, y))] = find(id(x - 1, y));
            edge[id(x, y)].push_back(id(x - 1, y));
            edge[id(x - 1, y)].push_back(id(x, y));
        }
        for(int y = b[i] + 1; y <= d[i]; ++y){
            fa[find(id(x, y))] = find(id(x, y - 1));
            edge[id(x, y)].push_back(id(x, y - 1));
            edge[id(x, y - 1)].push_back(id(x, y));
        }
    }
    for(int i = 1; i <= cnt; ++i){
        std::sort(edge[i].begin(), edge[i].end());
        int tmp = (std::unique(edge[i].begin(), edge[i].end()) - edge[i].begin()) & 1;
        ans[find(i)] += tmp;
    }
    int ret = -1;
    for(int i = 1; i <= cnt; ++i) if(fa[i] == i && edge[i].size()) {
        if(m & 1) ret += std::max(ans[i] / 2 - 1, 0);
        ++ret;
    }
    printf("%d\n", ret);
    return 0;
}
posted @ 2022-11-17 19:27  La_pluma  阅读(51)  评论(0编辑  收藏  举报