这道题好像需要用到线段树喵!

如果不会的话先听小科普:

线段树是一棵二叉树,每个节点代表一个区间[l, r]。如果这个区间还能划分(即l < r),那么它就有左右两个孩子,分别代表区间[l, mid]和[mid+1, r],其中mid = (l + r) / 2。

由于是二叉树的缘故,若它的下标为n,那么它的左右孩子的下标分别等于2*n和2*n+1喵~

(用爪子轻轻拍拍代码)你看,我们有一个好~~长的街道,街上每个房子都住着一只猫猫,有的在睡觉(0),有的在玩毛线球(1)。我们猫猫巡逻队要管理整条街呢!喵~

猫猫巡逻队的组织结构(node)

struct node
{
    int l, r;      // 本队猫猫负责的街道范围
    int sum;       // 这个范围里有多少只在玩毛线球的猫猫
    int lazy = 0;     // 小本本上记的之前偷了几次懒
};
  • 每个队长都有管理范围
  • 队长不用亲自数猫,问在自己范围内队长就行了喵~

建立巡逻队(build函数)

(舔毛)一开始,猫猫们要排好队伍喵!

void build(...) {
    tree[rt].l = l;  // 本队长负责从l号房
    tree[rt].r = r;  // 到r号房喵!
    if(l == r) {     // 如果只管一个房子
        tree[rt].sum = data[l];  // 记下这只猫在玩球还是睡觉
        return;
    }
    int mid = (l + r) / 2;
    build(data, tree, 2*rt, l, mid);
    build(data, tree, 2*rt+1, mid+1, r);
    // 把地盘分给两个副队长去管~
    tree[rt].sum = tree[2*rt].sum + tree[2*rt+1].sum;
    // 然后本队长只要记住:玩球的猫总数 = 左队的 + 右队的
}

(如果不懂懒标记可以先去结算偷懒那里弄清楚懒标记是什么意思)

要变天了喵!(反转flip)

(眼睛眯成一条缝)天黑啦,主人说要把第l到第r房的猫猫状态都变一变!

void flip(...) {
    if(fl <= l && fr >= r) {  // 如果任务完全在我的管辖范围内
        // 玩球的猫猫数 = 总猫数 - 现在玩球的猫数
        tree[rt].sum = (r-l+1) - tree[rt].sum;
        // 因为玩球的会去睡觉,睡觉的会来玩球喵!
        tree[rt].lazy ^= 1;  // 在小本本上记一笔“这次先偷懒,问我的时候再变化”
                //因为偷两次懒等于没有偷懒
        return;  // 记完就去睡回笼觉啦~ zZZ
    }
    
    pushDown(rt);  // 先看看小本本有没有欠其他猫的任务
    
    if(fl <= mid) flip(tree, 2*rt, l, mid, fl, fr);
    if(fr > mid) flip(tree, 2*rt+1, mid+1, r, fl, fr);
    // 让左副队长和右副队长去执行各自的部分
    tree[rt].sum = tree[2*rt].sum + tree[2*rt+1].sum;
    // 最后更新本队长的记录
}

 检查小本本(pushDown函数)(清算偷懒)

(竖起耳朵,认真状)当需要深入查看时,就要处理小本本了喵:

void pushDown(int rt) {
    if(tree[rt].lazy == 1) {  // 哎呀,小本本上有待办事项!
        // 通知左副队长:“你管的区域要反转啦!”
        tree[left].sum = (它的辖区大小) - tree[left].sum;
        tree[left].lazy ^= 1;  // 让它也记在小本本上
        // 通知右副队长同样的事~
        (略)
        tree[rt].lazy = 0;  // 擦掉本子上的记录,任务下派啦!
    }
}

效率高的原因

(得意地摇尾巴)普通的笨方法是一只只猫去查看和改变,要跑好多路喵!但是我们猫猫巡逻队超聪明:

1,任务来了,只通知到刚好能全权负责的最小队长

2,队长在小本本上记一笔就不管了,继续打盹

3,只有真的需要详细了解时,才把任务下派

(懒惰标记作用)

(蹭蹭你的手)这样处理n=50万,q=50万的大数据时,我们的方法比笨方法快了几万倍呢!喵呜~

(用爪子画示意图)其实就是:

    线段树 = 猫猫们的层级管理体系

    懒标记 = 队长们偷懒用的小本本

    按需下传 = 真的需要时才去跑腿

(开心地转圈)这样既能快速回答主人的问题,又能高效执行任务,还有更多时间睡懒觉!完美喵~ ♪(^∇^*)

   如果还有不理解的地方看猫猫的代码一步一步推就可以了喵!

   好长的题解,猫猫燃尽了喵~

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

struct node
{
    int l,r;//该结点的左坐标和右坐标
    int sum,lazy=0;//[l,r]范围内1的个数和懒标记
};

void build(const vector<int> &data,vector<node> &tree,int rt,int l,int r)
{
    tree[rt].l=l;
    tree[rt].r=r;
    //初始化结点的左右端点坐标
    if(l==r)
    {
        tree[rt].sum=data[l];
        return;
    }
    int mid=(l+r)/2;
    build(data,tree,2*rt,l,mid);//递归构建左孩子
    build(data,tree,2*rt+1,mid+1,r);//递归构建右孩子
    tree[rt].sum=tree[2*rt].sum+tree[2*rt+1].sum;
    //1的数量为左右孩子(即两个互补子集)数量之和
}

void pushDown(vector<node> &tree,int rt)//更新懒标记并下派懒标记
{
    if(tree[rt].lazy==1)
    {
        int left=2*rt,right=2*rt+1;
        //更新左孩子
        tree[left].sum=(tree[left].r-tree[left].l+1)-tree[left].sum;
        tree[left].lazy^=1;
        //更新右孩子
        tree[right].sum=(tree[right].r-tree[right].l+1)-tree[right].sum;
        tree[right].lazy^=1;
        //清空当前标记
        tree[rt].lazy=0;
    }
}

int query(vector<node> &tree,int rt,int l,int r,int ql,int qr)
//ql,qr是查询区间的左右边界;查询[ql,qr]之间1的数量
{
    if(ql<=l&&qr>=r)//如果左右端被包括其中
    {
        return tree[rt].sum;//直接返回值
    }
    pushDown(tree,rt);//更新懒标记
    int mid=(l+r)/2;
    int res=0;
    //查询左端必须小于等于mid才有向左查询的必要,右端同理
    if(ql<=mid) res+=query(tree,2*rt,l,mid,ql,qr);
    if(qr>=mid+1) res+=query(tree,2*rt+1,mid+1,r,ql,qr);
    return res;
}

void flip(vector<node> &tree,int rt,int l,int r,int fl,int fr)
//fl,fr是反转区间的左右边界;反转[ql,qr]区间
{
    if(fl<=l&&fr>=r)//如果左右端被包括其中
    {
        tree[rt].sum=(r-l+1)-tree[rt].sum;
        //反转后1的总数一定等于区间长度减去反转前1的总数
        tree[rt].lazy^=1;
        //向下做懒惰标记
        return;
    }
    pushDown(tree,rt);
    int mid=(l+r)/2;
    if(fl<=mid) flip(tree,2*rt,l,mid,fl,fr);
    if(fr>mid) flip(tree,2*rt+1,mid+1,r,fl,fr);
    tree[rt].sum=tree[2*rt].sum+tree[2*rt+1].sum;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    int n,q;
    cin >> n >> q;
    string s;
    cin >> s;
    vector<int> data(n+1);//储存01串
    for(int i=0;i<s.size();i++)
    {
        data[i+1]=s[i]-'0';
    }
    //初始化
    vector<node> tree(4*n+5);//线段树范围差不多是01串的4倍
    build(data,tree,1,1,n);//构建线段树
    while(q--)
    {
        int op,l,r;cin >> op >> l >> r;
        if(op==1) flip(tree,1,1,n,l,r);//反转
        else if(op==2) cout << query(tree,1,1,n,l,r) <<'\n';//数一
    }
}
/*
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⣤⡀⣀⣠⣤⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⡀⢀⣴⣾⣷⣶⣾⣿⣿⣿⣿⣿⣿⣿⣿⣷⣾⣿⣷⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⠿⠛⠛⠉⠉⠉⠉⠉⠉⠛⠻⠿⣿⣿⣿⣿⣿⣶⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⢠⣾⣿⣿⣿⡿⠿⠛⠉⠉⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠿⣿⣿⣿⣷⣄⡀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⣀⣿⣿⣿⠟⠁⠀⠀⠀⠀⠀⠀⠀⣰⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⣠⣾⣿⣿⠟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣶⣄⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣻⣿⣿⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⢹⣿⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⠁⠈⢢⡀⠀⠀⠀⢸⡇⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⣿⡟⠒⢦⡀⠀⠀⠀
⠀⠀⣠⣤⣤⣼⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⡇⠀⠀⠀⠉⢢⣄⠀⠀⢿⠊⠳⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⣷⡄⠀⢷⠀⠀⠀
⠀⢰⠇⠀⣰⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⡌⣹⠗⠦⣬⣇⠀⠉⢢⡀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⡀⢸⡄⠀⠀
⠀⡟⠀⣼⣯⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⣆⢹⡀⠀⠀⠀⠉⠁⠀⠀⢀⣀⡁⠀⠀⠉⠳⢴⡆⠀⠀⠀⠀⠀⠀⢹⣧⠈⡇⠀⠀
⠀⡇⠀⠀⢻⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣾⠻⠉⠛⠂⠀⠀⠀⠀⠀⠀⠻⠿⣿⣿⣿⣶⣦⡀⠛⣇⠀⠀⠀⠀⠀⣈⣿⠀⡇⠀⠀
⢸⡇⠀⠀⢠⣿⣷⣦⣀⡸⣷⣦⣶⡂⠉⠉⠉⢁⣤⣶⡶⠀⠀⠀⣀⣀⡴⠀⠀⠀⠀⠀⠀⠈⠉⠉⠁⠀⡟⢀⣴⣟⣰⣾⣿⣏⣠⠇⠀⠀
⠈⡇⠀⠀⢸⣿⠁⠉⣿⠛⠛⠃⡇⠀⠀⢠⣶⣿⡿⠛⠁⠀⠀⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠼⢿⠟⠿⢿⡏⠀⠘⣿⡀⠀⠀⠀
⠀⢷⣀⣀⣿⠇⠀⠀⢿⡇⠀⢀⢱⡀⠀⠛⠛⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⠀⠀⢸⠇⠀⠀⢹⣿⣄⠀⠀
⠀⠀⣉⣿⡏⠀⠀⠀⠀⠀⠀⢸⣇⣳⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡰⣿⠃⠀⠀⠀⠀⠀⠀⣿⠈⢧⠀
⠀⠘⣿⣿⠁⠀⠀⠀⠀⠀⠀⠘⣿⡛⣶⠀⠀⣠⠔⠒⠛⠒⠦⡀⠀⠀⠀⠀⣠⡤⠶⠤⢤⣀⠀⠀⠀⢀⣏⡄⠀⠀⠀⠀⠀⡀⣿⡆⠈⣧
⣠⡾⠛⣿⣿⣧⠀⠀⠀⠀⢸⣿⠾⢿⡿⠀⣰⠃⠀⠀⠀⠀⠀⢹⡄⠀⠀⡼⠁⠀⠀⠀⠀⠈⠙⣦⠀⢸⣿⡇⣾⣣⡀⠀⢰⣿⣿⣿⣤⠾
⡟⠀⠀⠻⣿⡟⢷⡄⣤⡀⠈⣿⡀⣸⠇⠀⠏⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⡇⢀⡀⠀⠀⠀⠀⢀⡟⠀⠀⠋⣿⣿⣿⡇⣠⣿⠿⠛⢷⡀⠀
⠀⠀⠀⠀⣿⣇⣨⣿⣿⣿⣦⣽⣷⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠁⠀⠀⠃⠀⠙⠢⠤⠤⠴⢾⠀⠀⠀⠀⢸⣷⣿⣿⠟⠁⠀⠀⠈⣧⠀
⠀⠀⠀⠀⠈⠉⠉⠁⠈⠉⠈⢉⣿⡁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠀⠀⠀⠀⢸⡇⠀⠀⠀⠀⠀⠀⠀⣿⠀
*/