bzoj4742 NOI2017整数

不会考试题来补一手题解

题目背景

在人类智慧的山巅,有着一台字长为10485761048576 位(此数字与解题无关)的超级计算机,著名理论计算机科

学家P博士正用它进行各种研究。不幸的是,这天台风切断了电力系统,超级计算机

无法工作,而 P 博士明天就要交实验结果了,只好求助于学过OI的你. . . . . .

题目描述

P 博士将他的计算任务抽象为对一个整数的操作。

具体来说,有一个整数xx ,一开始为00 。

接下来有nn 个操作,每个操作都是以下两种类型中的一种:

  • 1 a b:将xx 加上整数a\cdot 2^ba?2b ,其中aa 为一个整数,bb 为一个非负整数
  • 2 k :询问xx 在用二进制表示时,位权为2^k2k 的位的值(即这一位上的11 代表 2^k2k )

保证在任何时候,x\geqslant 0x?0 。

输入输出格式

输入格式:

输入的第一行包含四个正整数n,t_1,t_2,t_3n,t1?,t2?,t3? ,nn 的含义见题目描述,t_1t1? ,t_2t2? ,t_3t3? 的具体含义见子任务。

接下来nn 行,每行给出一个操作,具体格式和含义见题目描述。

同一行输入的相邻两个元素之间,用恰好一个空格隔开。

输出格式:

对于每个询问操作,输出一行,表示该询问的答案(00 或11 )。对于加法操作,没有任何输出。

说明

n<=1e6

a<=1e9

b<= 30*n

考虑一般做法

建一颗大小为30*n的线段树

将a的每一位拆开,然后在线段树里找下一个1或0

复杂度O(30n*log30n)

看起来很慢

其实常数够优秀的话是可以A的

对于非wys选手来说

其实不需要一位一位的计算

一个叶子节点记录30位

将a拆成2个数

因为进位退位最多为1

在线段树上面找出一个点以后最长连续一段的0或者(1<<30)-1

然后就做完了

复杂度O(nlogn)

代码不长,挺好写的,就300行左右,也就是一个上午的时间就可以对着大数据调出来啦

//%std
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<set>
#include<map>
using namespace std;
#define lovelive long long
#define lc son[x][0]
#define rc son[x][1]
#define lowbit(x) (x&(-x))
#define pt vc
const int N=1e6+100;
void read(int &x)
{
  int p=1;
  x=0;
  char c=getchar();
  while(c<‘0‘||c>‘9‘)
  {
    if(c==‘-‘)
      p=-1;
    c=getchar();
  }
  while(c>=‘0‘&&c<=‘9‘)
  {
      x=x*10+c-48;
      c=getchar();
  }
  x*=p;
}
struct tree{
  int l,r;
  bool sum1,sum0;
  int lazy,key;
}t[N*4];

void buildtree(int i,int l,int r)
{
  t[i].l=l;
  t[i].r=r;
  t[i].lazy=-1;
  t[i].sum0=1;
  if(l==r)
    return;
  int mid=(l+r)>>1;
  buildtree(i<<1,l,mid);
  buildtree(i<<1|1,mid+1,r);
}
void pushdown(int i)
{
  if(t[i].lazy==-1)
    return;
  t[i<<1].sum0=t[i<<1|1].sum0=t[i].lazy^1;
  t[i<<1].sum1=t[i<<1|1].sum1=t[i].lazy;
  t[i<<1].lazy=t[i<<1|1].lazy=t[i].lazy;
  if(t[i<<1].l==t[i<<1].r)
    t[i<<1].key=t[i].lazy? (1<<30)-1:0;
  if(t[i<<1|1].l==t[i<<1|1].r)
    t[i<<1|1].key=t[i].lazy? (1<<30)-1:0;
  t[i].lazy=-1;
}

void change(int i,int pos,int x)
{
  if(t[i].l>pos||t[i].r<pos)
    return;
  if(t[i].l==t[i].r)
  {
      t[i].sum0=t[i].sum1=0;
      t[i].key=x;
      if(x==((1<<30)-1))
        t[i].sum1=1;
      else
        if(!x)
          t[i].sum0=1;
      return;
  }
  pushdown(i);
  change(i<<1,pos,x);
  change(i<<1|1,pos,x);
  t[i].sum1=t[i<<1].sum1&t[i<<1|1].sum1;
  t[i].sum0=t[i<<1].sum0&t[i<<1|1].sum0;
}
void change_(int i,int l,int r,int x)
{
  if(l>t[i].r||r<t[i].l)
    return;
  if(l<=t[i].l&&t[i].r<=r)
  {
    t[i].sum1=x;
    t[i].sum0=x^1;
    t[i].lazy=x;
    if(t[i].l==t[i].r)
      t[i].key=x? (1<<30)-1:0;
    return;
  }
  pushdown(i);
  change_(i<<1,l,r,x);
  change_(i<<1|1,l,r,x);
  t[i].sum1=t[i<<1].sum1&t[i<<1|1].sum1;
  t[i].sum0=t[i<<1].sum0&t[i<<1|1].sum0;
}

int find_key(int pos)
{
  int i=1;
  while(1)
  {
    if(t[i].l==t[i].r)
      return t[i].key;
    pushdown(i);
    if(pos<=t[i<<1].r)
      i=i<<1;
    else
      i=i<<1|1;
  }
 }
int find_num1(int pos)
{
  int i=1,r=1,pre;
  while(t[i].l!=t[i].r)
  {
      pushdown(i);
    if(pos<=t[i<<1].r)
      i=i<<1;
    else
      i=i<<1|1;
  }
  if(!t[i].sum1)
    return 0;
  pre=i;
  i>>=1;
  while(i!=1)
  {
    if(!(pre&1))
    {
      if(!t[pre^1].sum1)
        break;
      else
          r+=t[pre^1].r-t[pre^1].l+1;
    }
    pre=i;
    i>>=1;
  }
  if(pre==(i<<1|1))
    return r;
  i=i<<1|1;
  while(1)
  {
    if(t[i].l==t[i].r)
    {
      r+=t[i].sum1;
      break;
    }
    pushdown(i);
      if(t[i<<1].sum1)
        r+=t[i<<1].r-t[i<<1].l+1,i=i<<1|1;
      else
        i=i<<1;
  }
  return r;
}
int find_num0(int pos)
{
  int i=1,r=1,pre;
  while(t[i].l!=t[i].r)
  {
      pushdown(i);
    if(pos<=t[i<<1].r)
      i=i<<1;
    else
      i=i<<1|1;
  }
  if(!t[i].sum0)
    return 0;
  pre=i;
  i>>=1;
  while(i!=1)
  {
    if(!(pre&1))
    {
      if(!t[pre^1].sum0)
        break;
      else
          r+=t[pre^1].r-t[pre^1].l+1;
    }
    pre=i;
    i>>=1;
  }
  if(pre==(i<<1|1))
    return r;
  i=i<<1|1;
  while(1)
  {
    if(t[i].l==t[i].r)
    {
      r+=t[i].sum0;
      break;
    }
    pushdown(i);
      if(t[i<<1].sum0)
        r+=t[i<<1].r-t[i<<1].l+1,i=i<<1|1;
      else
        i=i<<1;
  }
  return r;
}
void add(int pos,int a)
{
  int tmp=find_key(pos);
  if(tmp+a<(1<<30))
    change(1,pos,tmp+a);
  else
  {
    change(1,pos,tmp+a-(1<<30));
    tmp=find_num1(pos+1);
    if(tmp)
      change_(1,pos+1,pos+tmp,0);
    change(1,pos+tmp+1,find_key(pos+tmp+1)+1);
  }
}
void del(int pos,int a)
{
  int tmp=find_key(pos);
  if(tmp>=a)
    change(1,pos,tmp-a);
  else
  {
    change(1,pos,tmp-a+(1<<30));
    tmp=find_num0(pos+1);
    if(tmp)
      change_(1,pos+1,pos+tmp,1);
    change(1,pos+tmp+1,find_key(pos+tmp+1)-1);
  }
}
int main()
{
  int n,fk,opt,a,b,x,y,tmp,a1,a2;
//  freopen("test.in","r",stdin);
//  freopen("test.out","w",stdout);
  read(n);read(fk);read(fk);read(fk);
  buildtree(1,1,n+1);
  for(int i=1;i<=n;i++)
  {
    if(i==4501)
      i--,i++;
      read(opt);
      if(opt&1)
      {
      read(a);read(b);
      x=b/30+1;
      y=b%30;
      if(a<0)
      {
          a=-a;
        a1=(a<<y)&((1<<30)-1);
        a2=a>>(30-y);
        del(x,a1);
        if(a2)
          del(x+1,a2);
      }
      else
      {
          a1=(a<<y)&((1<<30)-1);
        a2=a>>(30-y);
        add(x,a1);
        if(a2)
          add(x+1,a2);
      }
    }
    else
    {
      read(a);
      x=a/30+1;
      y=a%30;
      tmp=find_key(x);
      cout<<(tmp>>y&1)<<"\n";
    }
  }
  return 0;
}
/*
10000 1 1 1
1 1 30000
1 1 100
1 -1 1000
1 1 10000
2 10000

10000 1 1 1
1 1 30000
1 -1 1000
1 1 10000
2 10000
*/

原文地址:https://www.cnblogs.com/NicoDafaGood/p/8856580.html

时间: 2024-11-04 01:45:25

bzoj4742 NOI2017整数的相关文章

【BZOJ4942】[Noi2017]整数 线段树+DFS(卡过)

[BZOJ4942][Noi2017]整数 题目描述去uoj 题解:如果只有加法,那么直接暴力即可...(因为1的数量最多nlogn个) 先考虑加法,比较显然的做法就是将A二进制分解成log位,然后依次更新这log位,如果最高位依然有进位,那么找到最高位后面的第一个0,将中间的所有1变成0,那个0变成1.这个显然要用到线段树,但是复杂度是nlog2n的,肯定过不去. 于是我在考场上yy了一下,这log位是连续的,我们每次都要花费log的时间去修改一个岂不是很浪费?我们可以先在线段树上找到这段区间

[BZOJ4942][NOI2017]整数(线段树+压位)

CCF的题经常是对于一个不是非常高级的算法或数据结构挖掘性质进行优化. 松爷的题总是充满常数优化气息,这个题也确实是在常数上做文章. 首先如果全加的话是可以直接暴力的,因为可以证明对每一位来说是均摊$O(1)$的,将a二进制分解一次. 然后将暴力想下去之后容易发现,二进制加法如果进位,肯定是找到这一位之前第一个为0的位加一,然后这两位之间的所有位都变成0(就是模拟竖式加法),减法反之. 于是这个东西就是前驱查找和区间修改,应用线段树解决,$O(n\log^{2}n)$. 但是看到极其相近的数据范

noi2017 T1 整数 ——线段树

loj.ac上有  题目传送门 不过我还是把题目搬过来吧 整数(integer)[题目背景]在人类智慧的山巅,有着一台字长为 1048576 位的超级计算机,著名理论计算机科 学家 P 博士正用它进行各种研究.不幸的是,这天台风切断了电力系统,超级计算机 无法工作,而 P 博士明天就要交实验结果了,只好求助于学过 OI 的你......[题目描述] P 博士将他的计算任务抽象为对一个整数的操作. 具体来说,有一个整数 x ,一开始为 0. 接下来有 n 个操作,每个操作都是以下两种类型中的一种:

【noi2017】 整数 线段树or模拟

ORZYYB 题目大意:你需要维护一个有$3\times 10^7$个二进制位的数,有一种修改方式和一种询问方式 对这个数加上$a\times2^b$,其中$|a|≤10^9$,$b≤3\times 10^7$,保证需要维护的这个数始终非负 询问这个数第k个二进制位的值 总共有$10^6$次询问/修改操作 我们不难发现,如果只有加法操作的话,对任意一个位执行加法操作,均摊进位次数是1. 证明是显然的(我貌似之前在MC里面用红石电路模拟过二进制进位过程....) 也就是说暴力加暴力进位的复杂度是正

C/C++算法竞赛入门经典Page11 例题1-5 三整数排序

题目:输入3个整数,从小到大排序后输出 样例输入: 20 7 33 样例输出: 7 20 33 首先,先声明三个整数a,b,c和一个临时变量t: int a,b,c,t;//1,b,c为三个整数,t为临时变量 输入三个整数: scanf("%d%d%d",&a,&b,&c); 进行3次if判断: 1.如果b>a,则a,b对调 2.如果c>a,则a,c对调 3.如果c>b,则b,c对调 代码: if(b>=a){ t=b; b=a; a=t

【BZOJ4945】[Noi2017]游戏 2-SAT

[BZOJ4945][Noi2017]游戏 题目描述 题解:2-SAT学艺不精啊! 这题一打眼看上去是个3-SAT?哎?3-SAT不是NPC吗?哎?这题x怎么只有8个?暴力走起! 因为x要么不是A要么不是B,所以直接2^8枚举所有x就行了.然后就变成了一个2-SAT问题.假设有两场游戏1,2,分别可以使用的地图为A1,A2,B1,B2,如果有一个限制是1 A 2 A,那么选A1就必须选A2,然后我这个沙茶就开开心心的拿了55分. 为什么不对?我建出来的图显然不对偶啊!考虑逆否命题,选A1就必须选

判断给定十进制整数的二进制形式中含有几个1

两种判断一个给定整数的二进制形式中含有几个1的简单方法: 主要思想是通过按位与(&)运算和位移运算(<< >>)实现 1 unsigned int_number( int n) 2 { 3 if (n < 0) 4 return; 5 unsigned count = 0; 6 while (n != 0) 7 { 8 if ((n & 1) != 0) 9 ++count; 10 n >>= 1; 11 } 12 return count; 13

2_C语言中的数据类型 (三)整数与无符号数

1.1       sizeof关键字 sizeof是c语言关键字,功能是求指定数据类型在内存中的大小,单位:字节 sizeof与size_t类型 1.1       int类型 1.1.1          int常量,变量 int就是32位的一个二进制整数,在内存当中占据4个字节的空间 1.1.2          printf输出int值 %d,输出一个有符号的10进制整数,%u,代表输出一个无符号的十进制整数 1.1.3          printf输出八进制和十六进制 %x,代表输出

linux整数比较

整数比较 -eq 等于,如:if [ "$a" -eq "$b" ] -ne 不等于,如:if [ "$a" -ne "$b" ] -gt 大于,如:if [ "$a" -gt "$b" ] -ge 大于等于,如:if [ "$a" -ge "$b" ] -lt 小于,如:if [ "$a" -lt "$b"