做题记录 2021.9.23 洛谷P2574XOR的艺术

题目大意:给定一个由01组成的字符串,对其某个区间进行反转(0变为1,1变为0)、查询1的个数两种操作,对于每次查询输出该区间1的个数。

思路:直接暴力做必定TLE,原本思路为:分块处理,设置num0与num1表分表示0与1的个数,设置一个vis数组表示被整体修改的次数。
1.当对块内部分元素进行修改时,若区间对应的vis为奇数,则再修改一次就改回来了,故不修改;为偶数则修改。
2.对整块进行修改时,交换num0与num1的值,vis++。
3.对块内部分元素进行查询时,若vis为奇数,则加上这部分元素中0的个数,否则加上1的个数。
4.对整块进行查询时,直接加上该块num1的值。
代码如下:

block b;
inline bool check(int i,int m) {return (b.vis[m]&1)?s[i]=='0':s[i]=='1';}  //应该加一
inline void change(int i,int m) {
    if(b.vis[m]&1) {
    	if(s[i]=='0') {
    		b.num1[m]--;
    		b.num0[m]++;
		}
		else {
			b.num0[m]--;
			b.num1[m]++;
		}
	}
	else {
		if(s[i]=='0') {
			s[i]='1';
			b.num1[m]++;
			b.num0[m]--;
		}
		else {
			s[i]='0';
			b.num0[m]++;
			b.num1[m]--;
		}
	}
}

inline void update(int l,int r) {
    int st=l/b.block_size,end=r/b.block_size;
    if(st==end) {
        for(int i=l; i<=r; i++)    change(i,st);
        return;
    }
    if(l==b.st_index(st)) {
        b.vis[st]++;
        swap(b.num0[st],b.num1[st]);
    }
    else {
        int tmp=b.end_index(st);
        for(int i=l; i<=tmp; i++)    change(i,st);
    }
    for(int i=st+1; i<end; i++) {
        swap(b.num0[i],b.num1[i]);
        b.vis[i]++;
    }
    if(l==b.end_index(end)) {
        b.vis[end]++;
        swap(b.num0[end],b.num1[end]);
    }
    else {
        int tmp=b.st_index(end);
        for(int i=tmp; i<=r; i++)    change(i,end);
    }
}

inline int query(int l,int r) {
    int res=0;
    int st=l/b.block_size,end=r/b.block_size;
    if(st==end) {
        for(int i=l; i<=r; i++)    res+=(int)check(i,st);
        return res;
    }
    if(l==b.st_index(st))   res+=b.num1[st];
    else {
        int tmp=b.end_index(st);
        for(int i=l; i<=tmp; i++)    res+=(int)check(i,st);
    }
    for(int i=st+1; i<end; i++)    res+=b.num1[i];
    if(r==b.end_index(end))    res+=b.num1[end];
    else {
        int tmp=b.st_index(end);
        for(int i=tmp; i<=r; i++)   res+=(int)check(i,end);
    }
    return res;
}

但以上方法只能得10分。经测试发现 先对整块进行修改,再对块内部分元素进行修改,再对块内部分元素进行查询就会出错,因为查询时不知道被查询的部分是否被“部分修改”过。

正确思路为: 1.进行“整块查询”时如果vis为奇数就计算0的个数,否则计算1的个数。

2.进行“整块修改”时避免改变num0与num1的值,只修改vis。

以上2点保证即使后面进行了“部分修改”也不会出错。

可以将vis设为bool型,并去掉num0减少空间使用。
代码如下:

#include <cstdio>
#include <algorithm>
#include <cmath>
const int M=(int)2e4+1,MAX_BLOCK=449;
using namespace std;
struct block {
    int num1[MAX_BLOCK];
    bool vis[MAX_BLOCK];
    int block_size,num_block;
    block() {
        for(int i=0; i<MAX_BLOCK; i++)	num1[i]=0,vis[i]=0;
    }
    inline int st_index(int m) {return m*block_size;}
    inline int end_index(int m) {return st_index(m+1)-1;}
};
block b;
char a[M];
inline bool check(int i,int m) {return (b.vis[m])?a[i]=='0':a[i]=='1';}  //应该加一
inline void change(int i,int m) {
    if(a[i]=='0') {
    	a[i]='1';
    	b.num1[m]++;
	}
	else {
		a[i]='0';
		b.num1[m]--;
	}
}

inline void update(int l,int r) {
    int st=l/b.block_size,end=r/b.block_size;
    if(st==end) {
        for(int i=l; i<=r; i++)    change(i,st);
        return;
    }
    int tmp=b.end_index(st);
    for(int i=l; i<=tmp; i++)    change(i,st);
    //整体修改时并不改变num0与num1的实际值
    for(int i=st+1; i<end; i++)    b.vis[i]=!b.vis[i];
    tmp=b.st_index(end);
    for(int i=tmp; i<=r; i++)    change(i,end);
}

inline int query(int l,int r) {
    int res=0;
    int st=l/b.block_size,end=r/b.block_size;
    if(st==end) {
        for(int i=l; i<=r; i++)    res+=(int)check(i,st);
        return res;
    }
    int tmp=b.end_index(st);
    for(int i=l; i<=tmp; i++)    res+=(int)check(i,st);
    for(int i=st+1; i<end; i++) {
    	if(!b.vis[i])    res+=b.num1[i];
    	else	res+=b.block_size-b.num1[i];
	}
    tmp=b.st_index(end);
    for(int i=tmp; i<=r; i++)   res+=(int)check(i,end);
    return res;
}

int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    scanf("%s",a);
    b.block_size=(int)sqrt(n),b.num_block=n/b.block_size+1;
    int S=b.block_size;
    for(int i=0; i<n; i++) {
    	if(a[i]=='1')	b.num1[i/S]++;
	}
    while(m--) {
        int op,l,r;
        scanf("%d%d%d",&op,&l,&r);
        if(op==0)    update(l-1,r-1);
        else    printf("%d\n",query(l-1,r-1));
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值