2024牛客多校 4 (概率,带权并查集,构造)

2024牛客多校 4 (概率,带权并查集,构造)

J - Zero

题面:给出整数 nk , 一个 0 1 ? 字符串 s

? 有概率是 0 或是 1,且概率相等,一段区间 [l,r] 的贡献这样计算:

  • 这一段区间不包含 0

  • 贡献为长度的 k 次方

求这个字符串 s 的期望贡献是多少?

solution:

首先应该考虑如何统计贡献,如果遍历区间,到达了复杂度 O(n2) 复杂度。

于是自然想到统计每一位的贡献,开始想到统计第 i 位出现的所有区间的贡献,但尝试过之后发现难以实现,重点来了,计算第 i 位结尾所得的贡献。

dp[i] 表示第 i 位结尾所得的贡献 ,于是列式计算:

若子串为 ????

f[3] 表示为 1211k+1222k+1233k

f[4]= 1211k+1222k+1233k+1244k

自然想到如何从 f[3] 变化到 f[4] ,这个时候联想一下数组推移的样子,首先多了一项长度为 1 的,其次前面每一项都长度加 1 ,用 f[4] 减去 f[3] 可得一个简单的式子:

(x+1)kxk=i=0k1Ckixi

所以发现需要计算 k 次方的答案需要用到 0 - (k - 1) 的结果,于是升级定义

定义 dp[i][j] 表示第 i 位结尾,k=j 时的贡献

细节为底层特殊情况应该需要特殊判断去赋值,然后递推公式就是:

dp[i][j]={dp[i1][j]+1+i=0k1Ckidp[i1][i],s[i]=112(dp[i1][j]+1+i=0k1Ckidp[i1][i]),s[i]=?

参考代码如下:

// Created by qyy on 2024/9/13.

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

#define PII pair<int, int>
#define endl "\n"

const long long inf = 0x3f3f3f3f3f3f3f3f;
const int N = 2e5 + 10;
const long long mod = 998244353;

int n, k;
ll dp[N][32];
string s;

ll fast_pow(ll a, ll n){
    ll ans = a % mod;
    while(n){
        if(n & 1){
            ans = (ans * a) % mod;
        }
        a = (a * a) % mod;
        n >>= 1;
    }
    return ans;
}
ll inv(ll x){
    return fast_pow(x, mod - 2);
}

ll C[50][50];


ll cal(int beg, int ed){
    if(beg > ed){
        return 0;
    }
    int len = ed - beg + 1;
    for(int i = 0; i <= len; i++){
        for(int j = 0; j <= 30; j++){
            dp[i][j] = 0;
        }
        if(i != 0){
            dp[i][0] = 1;
        }
    }

    ll ans = 0;
    if(s[beg] == '?'){
        dp[1][0] = inv(2);
    }else{
        dp[1][0] = 1;
    }
    for(int j = 1; j <= k; j++){
        dp[1][j] = dp[1][j - 1];
    }
    for(int j = 0; j <= k; j++){
        for(int i = 2; i <= len; i++){
            int pos = beg + i - 1;
            ll res = 1;
            for(int x = 0; x < j; x++){
                ll tmp = C[j][x] * dp[i - 1][x];
                res = (res + tmp) % mod;
            }
            res = (res + dp[i - 1][j]) % mod;
            if(s[pos] == '?'){
                dp[i][j] = (res * inv(2)) % mod;
            }else{
                dp[i][j] = res;
            }
        }
    }
    for(int i = 1; i <= len; i++){
        ans = (ans + dp[i][k]) % mod;
    }
    return ans;
}

void solve() {
    cin >> n >> k;
    cin >> s;

    for(int i = 1; i <= 40; i++){
        C[i][0] = C[i][i] = 1;
        for(int j = 1; j < i; j++){
            C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
        }
    }
    s.push_back('0');
    int len = s.size();
    int beg = 0, ed = 0;
    ll ans = 0;
    for(int i = 0; i < len; i++){
        if(s[i] == '0'){
            ed = i - 1;
            ans = (ans + cal(beg, ed)) % mod;
            beg = i + 1;
        }
    }
    cout << ans % mod << endl;
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int t = 1;
    //cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}


A - LCT

题面:在一个连接成 n 个节点的树的过程中,询问当前任意一个根节点的最大深度是多少。

solution:显然用带权并查集来解决......

回顾 P1196 银河超级英雄,维护并查集中各点对根节点的距离。

P1196细节:

    int find_set(int x){
        int tmpfa = fa[x];
        if(fa[x] != x){
            fa[x] = find_set(fa[x]); // 先把当前父亲节点的更新了,再去计算
            dp[x] += dp[tmpfa];
        }
        return fa[x];
    }

需要先更新了当前父节点的状态才能推来,有点 $dp$ 的感觉


于是此题可以同样维护距离根节点距离数组,再针对根节点维护一个最大深度数组,于是考虑两个函数各自应该更新什么。

find_set(x) 应该路径压缩中,将当前根节点的答案累计到该点上。

merge_set(x, y) 应该增加 x 根节点的深度与更新 y 所在根节点的答案。

参考代码如下:

// Created by qyy on 2024/9/13.

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

#define PII pair<int, int>
#define endl "\n"

const long long inf = 0x3f3f3f3f3f3f3f3f;
const int N = 1e6 + 10;
const int mod = 7;

int n;
int fa[N], cnt[N], dp[N], ans[N];
void dsu_init(){
    for(int i = 1; i <= n; i++){
        fa[i] = i;
        cnt[i] = 1;
        dp[i] = 0;
        ans[i] = 0;
    }
}
int find_set(int x){
    int tmpfa = fa[x];
    if(fa[x] != x){
        fa[x] = find_set(fa[x]);
        dp[x] += dp[tmpfa];
    }
    return fa[x];
}
void merge_set(int x, int y){
    int fx = find_set(x), fy = find_set(y);
    if(fx != fy){
        ans[fy] = max(ans[fy], ans[fx] + dp[y] + 1);
        dp[x] += dp[y] + 1; // 链上的加法
        fa[fx] = fy;
    }
}
void solve() {
    cin >> n;
    dsu_init();
    for(int i = 1; i < n; i++){
        int a, b, c;
        cin >> a >> b >> c;
        merge_set(b, a);
        cout << ans[c] << " ";
    }
    cout << endl;
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}
posted @   9102700  阅读(16)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
  1. 1 鼓楼 赵雷
  2. 2 我们的歌 王力宏
  3. 3 老街 李荣浩
  4. 4 周杰伦
  5. 5 可惜没如果 林俊杰
  6. 6 不将就 李荣浩
  7. 7 南方姑娘 赵雷
  8. 8 南方姑娘(弹唱版) 赵雷
  9. 9 如果可以 韦礼安
  10. 10 写给黄淮 邵帅
  11. 11 我想念 汪苏泷
  12. 12 雨天 汪苏泷
  13. 13 雨天雨天 汪苏泷
  14. 14 成都 赵雷
我们的歌 - 王力宏
00:00 / 00:00
An audio error has occurred, player will skip forward in 2 seconds.

作词 : 王力宏/陈信延

作曲 : 王力宏

编曲 : 王力宏

已经听了一百遍

已经听了一百遍

怎么听都不会倦

从白天唱到黑夜

你一直在身边

如果世界太危险

只有音乐最安全

带着我进梦里面

让歌词都实现

无论是开心还是难过我的爱一直不变

不必担心时间流逝带走一切

无论是Hip-Hop 还是摇滚我的爱一直不变

所有美好回忆记录在里面

这种 Forever Love 那么深

我们的歌 那么真

无国界跨时代

再也不会叫我Kiss Goodbye

要每一句能够动人心弦 Yeah

情人总分分合合

可是我们却越爱越深

认识你 让我的幸福如此悦耳

能不能不要切歌

继续唱我们的歌​​

让感动一辈子都记得

已经听了一百遍

已经听了一百遍

怎么听都不会倦

从白天唱到黑夜

你一直在身边

如果世界太危险

只有音乐最安全

带着我进梦里面

让歌词都实现

无论是开心还是难过我的爱一直不变

不必担心时间流逝带走一切

无论是Hip-Hop 还是摇滚我的爱一直不变

所有美好回忆记录在里面

这种 Forever Love 那么深

我们的歌 那么真

无国界跨时代

再也不会叫我Kiss Goodbye

要每一句能够动人心弦 Yeah

情人总分分合合

可是我们却越爱越深

认识你 让我的幸福如此悦耳

能不能不要切歌

继续唱我们的歌​​

让感动一辈子都记得

情人总分分合合

情人总分分合合

可是我们却越爱越深

认识你 让我的幸福如此悦耳

能不能不要切歌

继续唱我们的歌​​

让感动一辈子都记得

电吉他/其他乐器:王力宏

鼓手:Eric Fawcrtt

贝斯:John Mumson

录音师/录音室:王力宏/Homeboy Music Studios,Taipei

OP:HIM Music Publishing Inc.

OP:Homeboy Music,Inc,Taiwan

SP:Sony Music Publishing(Pre)Ltd.Taiwan Branch

点击右上角即可分享
微信分享提示