[XJOI]noip44 T3还有这种操作

还有这种操作

ttt 最近在学习二进制, 他想知道小于等于N的数中有多少个数的二进制表示中有偶数个1。

但是他想了想觉得不够dark,于是他增加了若干次操作,每次操作会将一个区间内的0变1 , 1变0,

现在在每次操作之后,他都想知道原来的那个问题的答案。

输入格式:

第一行:一个01序列表示N的二进制表示 
第二行:操作次数 m 
接下来的m行每行两个数,表示要操作的区间。

输出格式:

输出m+1行,第一行表示初始的答案,第二行到第m+1行输出每次操作后的答案 
答案对1e9 + 7 取模

样例输入:

0
1
1 1

样例输出:

1
1

数据范围:

设 n为 N 的 位数

  • 10%, n <= 20, m <= 100
  • 30%, n <= 60, m <= 10000
  • 60%, n <= 1000, m <= 10000
  • 100%, n <= 1000000, m <= 100000

解题思路:

在看到n<=2^1000000时,我的内心是震惊的,想着或者是字典树,线段树

于是想着线段树方向想,但是怎么统计答案又成为了一个问题...

加之题目并没有说l<=r,于是这道题就愉快的爆零了

***下面是正题

我们想用一个线段树,维护每一位的翻转次数,以及每一位的答案的值

那么我们就可以用nlogn的时间建出它,并用mlogn的时间来查询。

好线段树部分讲完了

***下面是关于统计答案的

我们首先可以发现

[0,1)内有2^0=1个

[0,10)内有2^0=1个

[0,100)内有2^1=2个

[0,1000)内有2^2=4个(以上数均为二进制表示)

...

那么我们可以总结了,[0,100..00(n个0))内有2^(n-1)个数满足,此时n>=1(为什么下面会讲)

但是知道这个有什么用么?

我们想一个 1010=1000+10

答案就为[0,1000)+[0,10)+check(1010是否满足)

这个结论的证明留给读者去完成

***于是这个程序基本就成型了

注意:(1)l是可能大于r的

(2)在对于最后一位处理,需要特殊处理,否则,对于最后一位是1的数,会出现1的差别,

例如:101 如按原先方法计算,答案为2+1+check(101)=4,而正确答案是3

因此在遇到原串符合条件,且最后一位是1时,要对答案减一

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int N=5e6+10,p=1e9+7;
 4 struct xint {int l,r;}a[N];
 5 int val[N],sum[N],pw[N],bo[N],n[N],m,l,r;
 6 char s[N]; bool flag;
 7 void pushup(int rt,int len){
 8     val[rt]=(1ll*val[rt<<1]*pw[len]+val[rt<<1|1])%p;
 9     sum[rt]=sum[rt<<1]^sum[rt<<1|1];
10 }
11 void opera(int rt){
12     bo[rt]^=1; val[rt]=((pw[a[rt].r-a[rt].l+1]-1-val[rt])%p+p)%p;
13     sum[rt]=((a[rt].r-a[rt].l+1)&1)^sum[rt];
14 }
15 void pushdown(int rt){
16     if (bo[rt]) opera(rt*2),opera(rt*2+1),bo[rt]=0;
17 }
18 //--------------------------------
19 void build(int l,int r,int rt){
20     if (l>r) return; a[rt].l=l; a[rt].r=r;
21     if (l==r){
22         val[rt]=sum[rt]=n[l]; return;
23     }
24     int mid=(l+r)>>1;
25     build(l,mid,rt<<1); build(mid+1,r,rt<<1|1);
26     pushup(rt,r-mid);
27 }
28
29 void update(int l,int r,int rt){
30     if (l>r) return;
31     if (a[rt].l==l&&a[rt].r==r){
32         opera(rt); return;
33     }
34     int mid=(a[rt].l+a[rt].r)>>1; pushdown(rt);
35     if(r<=mid) update(l,r,rt<<1);
36         else if (l>mid) update(l,r,rt<<1|1);
37         else update(l,mid,rt<<1),update(mid+1,r,rt<<1|1);
38     pushup(rt,a[rt].r-mid);
39 }
40 int query(){
41     return (val[1]+bool(!sum[1]||flag))%p;
42 }
43 //------------------------------
44 int main(){
45     scanf("%s%d",s+1,&m); int len=strlen(s+1);
46     for (int i=len;i>=1;--i) n[i]=s[i]-‘0‘; pw[0]=1;
47     for (int i=1;i<=len;++i) pw[i]=pw[i-1]*2%p;
48     build(1,len-1,1); flag=n[len]; printf("%d\n",query());
49     while (m--){
50         scanf("%d%d",&l,&r);
51         if (l>r) swap(l,r);
52         if (r==len) r--,flag^=1;
53         update(l,r,1); printf("%d\n",query());
54     }
55 }

总结:个人觉得这是一道很好的线段树题目,当然对于我这种蒟蒻,线段树还不能熟练掌握的还要加油

***虽说联赛不考

时间: 2024-11-03 02:47:07

[XJOI]noip44 T3还有这种操作的相关文章

【翻译】MongoDB指南/CRUD操作(三)

[原文地址]https://docs.mongodb.com/manual/ CRUD操作(三) 主要内容: 原子性和事务(Atomicity and Transactions),读隔离.一致性和新近性,分布式查询(Distributed Queries),分布式写操作,模拟两阶段任务提交,在副本集中执行配额读取 1 原子性和事务(Atomicity and Transactions) 在MongoDB中,写操作在单文档级别具有原子性,即使修改一个文档中的多个嵌入式文档也是如此. 当一个写操作修

Oracle补全日志(Supplemental logging)

Oracle补全日志(Supplemental logging)特性因其作用的不同可分为以下几种:最小(Minimal),支持所有字段(all),支持主键(primary key),支持唯一键(unique),支持外键(foreign key).包括LONG,LOB,LONG RAW及集合等字段类型均无法利用补全日志. 最小(Minimal)补全日志开启后可以使得logmnr工具支持链式行,簇表和索引组织表.可以通过以下SQL检查最小补全日志是否已经开启:SELECT supplemental_

Oracle Supplemental 补全日志介绍

转. Oracle补全日志(Supplemental logging)特性因其作用的不同可分为以下几种:最小(Minimal),支持所有字段(all),支持主键(primary key),支持唯一键(unique),支持外键(foreign key).包括LONG,LOB,LONG RAW及集合等字段类型均无法利用补全日志. 最小(Minimal)补全日志开启后可以使得logmnr工具支持链式行,簇表和索引组织表.可以通过以下SQL检查最小补全日志是否已经开启: SELECT supplemen

双向循环链表-C语言版

源文件部分: #include<stdio.h> #include<string.h> #include<malloc.h> typedef int Elemtype; #include"Delist.h" int main() { Dlnode head=NULL; instruction(head); return 0; } 头文件部分: typedef struct DLnode { Elemtype data; struct DLnode *

【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验十四:储存模块

实验十四比起动手笔者更加注重原理,因为实验十四要讨论的东西,不是其它而是低级建模II之一的模块类,即储存模块.接触顺序语言之际,“储存”不禁让人联想到变量或者数组,结果它们好比数据的暂存空间. 1. int main() 2. { 3. int VarA; 4. char VarB; 5. VarA = 20; 6. VarB = 5; 7. } 代码14.1 如代码14.1所示,主函数内一共声明两个变量VarA与VarB(第3~4行).VarA是两个字节的整型变量,VarB是一个字节的字符变量

双向链表-C语言版

源文件部分: #include<stdio.h> #include<malloc.h> #include<string.h> typedef int Elemtype; #include"DList.h" int main() { Dlnode head=NULL; instruction(head); return 0; } 头文件部分: typedef struct DLnode { Elemtype data; struct DLnode *p

并发编程(2)线程管理

一.启动线程 void do_something(); std::thread t(do_something); 注意t是创建的线程,do_something是函数对象,不要传入函数.提供的函数对象会复制到新线程的存储空间当中,函数对象的执行和调用都在线程的内存空间中进行. join(),detach() 二.线程传参 线程调用传参 void f(int i,std::string const& s); std::thread t(f,3,"hello"); 注意线程初始化不会

DAY66-前端入门-javascript(十二) vue02

一.文本操作指令 采用文本指令后,页面标签的内容由vue实例来控制. 1.v-text <!-- 原文本会被msg替换 --> <p v-text='msg'></p> 2.v-html <!-- 可以解析带html标签的文本信息 --> <p v-html='msg'></p> 3.v-once <!-- v-once只能被赋值一次 --> <p v-once>{{ msg }}</p> 4.v-

java并发 - 学习CAS

学习CAS 一丶什么是CAS CAS(Compare And Swap) 即比较交换, 给定一个期望值, 当将要修改的变量值和期望值一致时(即其他线程没有修改), 就修改对应的变量值, 否则什么也不做, 它允许再次尝试比较交换, 直到成功为止. 二丶CAS算法过程 CAS(V,E,N). V表示要更新的变量, E表示预期值, N表示将要更新的值. 仅当V=E时, 才会将V更新为N ,  如果 V ≠ E, 说明有其他线程更新了V, 当前线程什么也不用做. 最后CAS返回当前V的真实值 三丶悲观锁