牛客周赛 Round 72 题解

牛客周赛 Round 72 题解

A 小红的01串(一)

直接遍历即可

#include <bits/stdc++.h>
using namespace std;

void solve() {
	string s; cin >> s;
    int n = s.size();
    int cnt = 0;
    for (int i = 1; i < n; i ++ ) {
        if (s[i] != s[i - 1]) cnt ++;
    }
    cout << cnt << endl;
}

int main() {
	int T = 1;
// 	cin >> T;
	while (T -- )
		solve();
	return 0;
} 

B 小红的01串(二)

可以看出,所有满足的串一定是 0101.. 或者 1010... 这样串的子串,所以我们找出这些串,对于长度为 m 的串,它们的所有长度为2的连续子串的个数为 1+2+...+m1,高斯公式统计即可。记得开 long long

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

void solve() {
	string s; cin >> s;
    int n = s.size();
    LL ans = 0;
    for (int i = 0; i < n; i ++ ) {
        int j = i + 1;
        while (j < n && s[j] != s[j - 1]) j ++;
        int len = j - i;
        ans += 1ll * len * (len - 1) / 2;
        i = j - 1;
    }
    cout << ans << endl;
}

int main() {
	int T = 1;
// 	cin >> T;
	while (T -- )
		solve();
	return 0;
} 

C 小红的01串(三)

首先考虑 1 的情况。

  • 如果 k=0 ,那么 a,b 中也得有一个为 0 ,否则就输出 1
  • 如果 k>0 ,那么需要对 a,b 分类讨论
    • a=b ,那么 k 的理论最大值是 a+b1 ,如果超出这个范围就是 1
    • ab ,那么 k 的理论最大值是 2×min(a,b) ,如果超出这个范围就是 1

接下来考虑构造。通过刚刚判断 1 的过程中可以看出,我们可以先通过 0101... 这样的方式凑出 k 来,多出来的 0 或者 1 找某个地方一起插进去即可。

如果直接写就会有一些问题:我们不知道是 0 开始构造还是 1 开始,需要写多个if,感觉很麻烦。

所以我们可以把这种操作封装成一个函数,具体看代码。

#include <bits/stdc++.h>
using namespace std;

int k;

void sort_01(int a, int b, int n1, int n2) {
    // 其中n1,n2一个为0一个为1,表示我们要填的数
    // 表示当前有a个n1和b个n2要填,其中我们想拿n1填第一个数
    int numa = k / 2 + 1;    // 要填numa个n1
    int numb = (k + 1) / 2;  // 要填numb个n2
    for (int i = 1; i <= a - numa; i ++ ) cout << n1; // 先将多余的n1输出
    cout << n1; // 输出主体部分中第一个n1
    if (k & 1) {
        // 如果是k是奇数,那么一定是n1开头n2结束,所以可以放心的把多余的n2放到尾部
        for (int i = 1; i <= k; i ++ ) {
            // 将n2和n1交替输出
            if (i & 1) cout << n2;
            else cout << n1;
        }
        // 输出多余的n2
        for (int i = 1; i <= b - numb; i ++ ) cout << n2;
    } else {
        // 如果k是偶数,说明是n1开头n1结束,不能把多余的n2放到尾部
        // 可以先不用输出最后一个n1
        for (int i = 1; i < k; i ++ ) {
            if (i & 1) cout << n2;
            else cout << n1;
        }
        // 在输出最后一个n1之前先把多余的n2输出
        for (int i = 1; i <= b - numb; i ++ ) cout << n2;
        // 再输出最后一个n1
        cout << n1;
    }
    cout << endl;
}

void solve() {
    int a, b;
    cin >> a >> b >> k;
    if (k == 0) {
        // k=0的特判
        if (a != 0 && b != 0) cout << -1 << endl;
        else {
            for (int i = 1; i <= a; i ++ ) cout << 0;
            for (int i = 1; i <= b; i ++ ) cout << 1;
            cout << endl;
        }
    } else {
        if (a == b && k > a + b - 1) {
            cout << -1 << endl;
            return ;
        }
        if (k > 2 * min(a, b)) cout << -1 << endl;
        else {
            // 如果0多,就先填0,否则先填1
            if (a >= b) sort_01(a, b, 0, 1);
            else sort_01(b, a, 1, 0);
        }
    }
}

int main() {
	int T = 1;
	cin >> T;
	while (T -- )
		solve();
	return 0;
} 

D 小红的01串(四)

有两种方法,我讲讲DP写法。

dpi 表示走到 i 时的最小花费。容易看出 dp1=0 ,可以从第 i 个位置转移到最近相同的字符处或者最近不同的字符处。这个信息我们可以开一个数组预处理得到。令 sufi,0/1 表示离 i 最近的 0/1 字符的位置, O(n) 预处理即可。

知道了以上信息后,我们得到以下状态转移式:

i 处为字符 0 时:

dpsufi,0=max(dpsufi,0,dpi+x)

dpsufi,1=max(dpsufi,1,dpi+y)

i 处为字符 1 时:

dpsufi,0=max(dpsufi,0,dpi+y)

dpsufi,1=max(dpsufi,1,dpi+x)

递推求即可。

时间复杂度 O(n)

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;

LL dp[N];
int suf[N][2];

void solve() {
	int n, x, y;
    cin >> n >> x >> y;
    string s; cin >> s;
    for (int i = 2; i <= n; i ++ ) dp[i] = 0x3f3f3f3f3f3f3f3f;
    int idx0 = -1, idx1 = -1;
    for (int i = n; i >= 1; i -- ) {
        suf[i][0] = idx0;
        suf[i][1] = idx1;
        if (s[i - 1] == '0') idx0 = i;
        else idx1 = i;
    }
    dp[1] = 0;
    for (int i = 1; i <= n; i ++ ) {
        char c = s[i - 1];
        if (c == '0') {
            if (suf[i][0] != -1) dp[suf[i][0]] = min(dp[suf[i][0]], dp[i] + x);
            if (suf[i][1] != -1) dp[suf[i][1]] = min(dp[suf[i][1]], dp[i] + y);
        } else {
            if (suf[i][1] != -1) dp[suf[i][1]] = min(dp[suf[i][1]], dp[i] + x);
            if (suf[i][0] != -1) dp[suf[i][0]] = min(dp[suf[i][0]], dp[i] + y);
        }
    }
    cout << dp[n] << endl;
}

int main() {
	int T = 1;
// 	cin >> T;
	while (T -- )
		solve();
	return 0;
} 

E 小红的01串(五)

一个比较套路的DP。我们可以用 dpi,j 表示到第 i 个字符,模 13 的余数为 j 时的方案数。

可以遍历字符串,当遍历到第 i 个字符时,有以下几种情况:

  • si=0 则说明当前位只能填 0 ,枚举上一个可能转移过来的状态,即可以从 dpi1,j 转移过来。由于上一个状态模 13 的余数为 j ,因此添上 0 以后模 13 的余数变成 j10%13 ,即 dpi,j10%13+=dpi1,j
  • si=1 则说明当前位只能填 1 ,枚举上一个可能转移过来的状态,即可以从 dpi1,j 转移过来。由于上一个状态模 13 的余数为 j ,因此添上 1 以后模 13 的余数变成 (j10+1)%13 ,即 dpi,(j10+1)%13+=dpi1,j
  • si=? 则说明即可以填 0 也可以填 1 ,把上面的转移写到一起即可。

时间复杂度 O(pn) 其中 p=13

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
const int MOD = 1e9 + 7;

LL dp[N][20];

void solve() {
	string s; cin >> s;
    int n = s.size();
    s = "+" + s;
    dp[0][0] = 1;
    for (int i = 1; i <= n; i ++ ) {
        if (s[i] != '?') {
            for (int j = 0; j < 13; j ++ ) {
                dp[i][(j * 10 + s[i] - '0') % 13] += dp[i - 1][j];
                dp[i][(j * 10 + s[i] - '0') % 13] %= MOD;
            }
        } else {
            for (int j = 0; j < 13; j ++ ) {
                dp[i][(j * 10 + 1) % 13] += dp[i - 1][j];
                dp[i][(j * 10 + 1) % 13] %= MOD;
                dp[i][(j * 10) % 13] += dp[i - 1][j];
                dp[i][(j * 10) % 13] %= MOD;
            }
        }
    }
//     for (int i = 1; i <= n; i ++ ) {
//         for (int j = 0; j < 13; j ++ ) cout << dp[i][j] << ' ';
//         cout << endl;
//     }
    cout << dp[n][0] << endl;
}

int main() {
	int T = 1;
// 	cin >> T;
	while (T -- )
		solve();
	return 0;
} 

bonus:类似的思想大家还可以做做洛谷的 P3131

F 小红的01串(六)

碎碎念:第一眼看题意,这不是非常经典的线段树么。然后开始写,都把 push_down 写完了,结果发现 n1e9 的,要加个离散化才行。

建议正在学线段树的同学都可以做做这题,如果能独立把代码调出来的话相信你的收获一定会很大。

我们先思考 n105 左右的情况。我们可以开一个线段树,维护以下信息:

  • 将当前区间修改为 0 开头的好串和 1 开头的好串的最小修改次数(分别记作 num0num1

首先考虑合并操作,我们在 push_up 的时候,当前节点的 num0 就是左子树的 num0 加上右子树的 num0 或者 num1 ,取决于左子树维护的区间长度。如果是奇数,说明 0 开头的好串一定是 1 结尾,那么右子树需要从 0 开头,因此需要用 num0 更新,另外一个情况同理。

再考虑更新操作,注意到有两个区间修改操作,先考虑单种:

  • 若只有区间修改为 1 的话可以考虑加个 lazyTag1 ,然后直接更新区间 num0num1
  • 若只有区间取反,可以考虑加个 lazyTag2 ,然后交换区间 num0num1 即可。

接下来考虑复合操作:

  • 若对一个区间先取反后修改为 1 ,相当于取反操作无效,只用管修改操作。
  • 若对一个区间先修改为 1 后再取反,相当于区间修改为 0,直接更新 num0num1

因此我们看得出来,对于一个区间的两个 lazyTag ,他们的先后顺序是有区别的,因此我们可以将两个 lazyTag 记作是第几次操作,方便我们更新。

这里需要注意一点:若我们在 push_down 的过程中,发现离当前区间的操作的最近操作是取反,并且当前又更新了一个取反操作,那么我们应该要做的是把取反的 lazyTag 给取消掉,而不是更新取反的 lazyTag ,否则会影响下面子区间的取反操作。

好了,分析完以后,发现 n109 ,所以我们不能像刚刚一样每个叶节点只维护一个位置,因此要让每个叶节点维护一个区间,例如我们的操作区间依次是 [1,6],[2,9],[6,10] ,先变成左闭右开的区间,即 [1,7),[2,10),[6,11) ,把端点记下来也就是 1,2,6,7,10,11 ,最后将相邻两个数之间合并成一个左闭右开的区间即可。[1,2),[2,6),[6,7),[7,10),[10,11) 。离散化后就可以转化了。

时间复杂度 O((q+n)logn)

#include <bits/stdc++.h>
#define ls (u << 1)
#define rs (u << 1 | 1)
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 3e5 + 10;

struct qy {
    int opt, l, r;
} a[N];
struct node {
    int l, r;       // 管理的左右区间,用离散化之后的值代替
    int ranL, ranR; // 表示管理的左右区间
    int num0, num1; // 将当前区间修改为0开头的好串和1开头的好串的修改次数
    int lazy_tag;   // 更新区间翻转的lazy是第几次操作
    int lazy_1;     // 更新区间为1的lazy是第几次操作
} tr[N << 2];

vector<int> v;
int getIdx(int x) {
    return lower_bound(v.begin(), v.end(), x) - v.begin() + 1;
}

void calc(int u, int lt, int l1) {
    int len = tr[u].ranR - tr[u].ranL + 1;
    if (lt && l1 && lt > l1) {
        // 先1后翻,等价于赋0
        tr[u].num0 = len / 2;
        tr[u].num1 = (len + 1) / 2;
    } else if (l1) {
        // 剩下的情况就是翻后1或者有1无翻,直接赋值为1
        tr[u].num0 = (len + 1) / 2;
        tr[u].num1 = len / 2;
    } else if (lt) {
        swap(tr[u].num0, tr[u].num1);
    }
}

void push_down(int u) {
    if (tr[u].lazy_1) tr[ls].lazy_1 = tr[rs].lazy_1 = tr[u].lazy_1;
    if (tr[u].lazy_tag) {
        // down下去取反操作时,注意看最近的操作是不是取反
        // 如果是的话就需要更新子区间的lazy_tag为0
        if (tr[ls].lazy_1 < tr[ls].lazy_tag) tr[ls].lazy_tag = 0;
        else tr[ls].lazy_tag = tr[u].lazy_tag;
        if (tr[rs].lazy_1 < tr[rs].lazy_tag) tr[rs].lazy_tag = 0;
        else tr[rs].lazy_tag = tr[u].lazy_tag;
    }
    calc(ls, tr[u].lazy_tag, tr[u].lazy_1);
    calc(rs, tr[u].lazy_tag, tr[u].lazy_1);
    tr[u].lazy_1 = tr[u].lazy_tag = 0;
}

void push_up(int u) {
    tr[u].ranL = tr[ls].ranL, tr[u].ranR = tr[rs].ranR;
    // 更新num0
    tr[u].num0 = tr[ls].num0;
    int len_l = tr[ls].ranR - tr[ls].ranL + 1;
    if (len_l & 1) tr[u].num0 += tr[rs].num1;
    else tr[u].num0 += tr[rs].num0;
    // 更新num1
    tr[u].num1 = tr[ls].num1;
    if (len_l & 1) tr[u].num1 += tr[rs].num0;
    else tr[u].num1 += tr[rs].num1;
}

void build(int u, int l, int r) {
    tr[u].l = l, tr[u].r = r;
    if (l == r) {
        tr[u].ranL = v[l - 1];
        tr[u].ranR = v[l] - 1;
        int len = tr[u].ranR - tr[u].ranL + 1;
        tr[u].num0 = len / 2;
        tr[u].num1 = (len + 1) / 2;
        return ;
    }
    int mid = (l + r) >> 1;
    build(ls, l, mid);
    build(rs, mid + 1, r);
    push_up(u);
}

void update(int u, int st, int ed, int opt, int num_opt) {
    // opt=1表示更新当前区间为1,否则就是翻转
    int l = tr[u].l, r = tr[u].r;
    if (st <= l && r <= ed) {
        if (opt == 1) {
            tr[u].lazy_1 = num_opt;
            calc(u, 0, num_opt);
        }
        if (opt == 2) {
            if (tr[u].lazy_1 < tr[u].lazy_tag) tr[u].lazy_tag = 0;
            else tr[u].lazy_tag = num_opt;
            calc(u, num_opt, 0);
        }
        return ;
    }
    push_down(u);
    int mid = (l + r) / 2;
    if (st <= mid) update(ls, st, ed, opt, num_opt);
    if (ed > mid) update(rs, st, ed, opt, num_opt);
    push_up(u);
}

node query(int u, int st, int ed) {
    int l = tr[u].l, r = tr[u].r;
    if (st <= l && r <= ed) return tr[u];
    push_down(u);
    int mid = (l + r) >> 1;
    node ans;
    ans.num0 = -1;
    if (st <= mid) {
        node tmp = query(ls, st, ed);
        ans = tmp;
    }
    if (ed > mid) {
        node tmp = query(rs, st, ed);
        if (ans.num0 == -1) ans = tmp;
        else {
            // 更新答案的num0
            int lenl = ans.ranR - ans.ranL + 1;
            if (lenl & 1) ans.num0 += tmp.num1;
            else ans.num0 += tmp.num0;
            // 更新答案的num1
            if (lenl & 1) ans.num1 += tmp.num0;
            else ans.num1 += tmp.num1;
            ans.ranR = tmp.ranR;
        }
    }
    return ans;
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("1.in", "r", stdin);
    freopen("1.out", "w", stdout);
#endif
    int m, q;
    cin >> m >> q;
    for (int i = 1; i <= q; i ++ ) {
        cin >> a[i].opt >> a[i].l >> a[i].r;
        v.push_back(a[i].l);
        v.push_back(a[i].r + 1);
    }
    sort(v.begin(), v.end());
    v.erase(unique(v.begin(), v.end()), v.end());
    int n = v.size() - 1;
    build(1, 1, n);
    for (int i = 1; i <= q; i ++ ) {
        int opt = a[i].opt;
        int l = getIdx(a[i].l) , r = getIdx(a[i].r + 1) - 1;
        if (opt == 1) update(1, l, r, 1, i);
        else if (opt == 2) update(1, l, r, 2, i);
        else {
            node ans = query(1, l, r);
            cout << min(ans.num0, ans.num1) << endl;
        }
    }
    return 0;
}
posted @   Time_Limit_Exceeded  阅读(11)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示