皮卡丘的梦想2(线段树+二进制状态压缩)

Description

一天,一只住在 501 实验室的皮卡丘决定发奋学习,成为像 LeiQ 一样的巨巨,于是他向镇上的贤者金桔请教如何才能进化成一只雷丘。

金桔告诉他需要进化石才能进化,并给了他一个地图,地图上有 n 个小镇,他需要从这些小镇中收集进化石。

接下来他会进行 q 次操作,可能是打听进化石的信息,也可能是向你询问第 l 个小镇到第 r 个小镇之间的进化石种类。

如果是打听信息,则皮卡丘会得到一个小镇的进化石变化信息,可能是引入了新的进化石,也可能是失去了全部的某种进化石。

如果是向你询问,你需要回答他第 l 个小镇到第 r 个小镇之间的进化石种类。

Input

首先输入一个整数 T (1 <= T <= 10),代表有 T 组数据。

每组数据的第一行输入一个整数 n (1 <= n <= 100000) 和一个整数 q (1 <= q <= 100000),分别代表有 n 个小镇,表皮卡丘有 q 次操作。

接下来输入 q 行,对于每次操作,先输入操作类型,然后根据操作类型读入:

  • 1: 紧接着输入 2 个整数 a (1 <= a <= n), b (1 <= b <= 60),表示第 a 个小镇引入了第 b 种进化石
  • 2: 紧接着输入 2 个整数 a (1 <= a <= n), b (1 <= b <= 60),表示第 a 个小镇失去了全部第 b 种进化石
  • 3: 紧接着输入 2 个整数 l, r (1 <= l <= r <= n),表示他想询问从第 l 个到第 r 个小镇上可收集的进化石有哪几种

Output

对于每组输入,首先输出一行 "Case T:",表示当前是第几组数据。

对于每组数据中的每次 3 操作,在一行中按编号升序输出所有可收集的进化石。如果没有进化石可收集,则输出一个 MeiK 的百分号 "%"(不包括引号)。

Sample Input

1
10 10
3 1 10
1 1 50
3 1 5
1 2 20
3 1 1
3 1 2
2 1 50
2 2 20
3 1 2
3 1 10

Sample Output

Case 1:
%
50
50
20 50
%
%

解题思路:刚好暑假学完了线段树,我一开始就把这道题当做线段树的裸题,用线段树来维护数据,不就是单点更新,区段查询嘛。但是很不幸时间超限,后来我考虑了原来每次查询进化石的时候还是要全部遍历一遍,所以是要超时的。这时我们就需要更高效的方法来存储和查询所有进化石的状态,这种方法就是利用二进制进行状态压缩。

我们用二进制数的每一位来表示每种进化石的有无,也即该小镇进化石的存在状态。例如,对于有 1、4 号进化石的小镇,我们可以用二进制数 1001 来表示。它的含义是:从右向左依次表示第 1 个到第 n 个进化石的有无,1 表示有,0 表示无。而且很容易想到,由于每一位只有 0 或 1 两种可能,且每一位都对应固定的编号,所以对于任意一个二进制数,都能保证唯一对应一种存在状态。

解决了如何表示存在状态的问题,下一步就是如何存储了。例如,当前我们的进化石存在状态为:1、4,对应二进制 1001,如果我们加入一个 3 号进化石,则应变为 1101,也就是让倒数第三位变成 1。这里需要用到位运算:对于 1001,我们让它与 0100(只含有 3 号石的状态)进行或运算,即两数对应的位有一个或两个为 1 时结果为1,否则为 0,运算结果为 1101。这样我们使用或运算就可以实现两个状态的合并。至于如何表示单个进化石的状态,很简单,使用左移运算就可以了,例如:表示 3 号石存在,只需将 1(0001)左移 3-1=2 位即得到 0100。

这样,我们只需要把二进制和线段树结合一下就可以愉快地告别 TLE 了。在存储时,每一个结点都表示它的左右子结点的合并状态,即对左右子结点进行或运算后的结果,而叶结点直接存储状态。在查询时,只需要遍历结果对应二进制的每一位来输出即可。

  1 #include<cstdio>
  2 #include<cstring>
  3 #include<algorithm>
  4 #define ll long long int
  5 const int MAXN=1e5+10;
  6 using namespace std;
  7 int n,q;
  8 ll sum[MAXN<<2];
  9 void push_up(int i)///向上回溯,状态合并
 10 {
 11     sum[i]=sum[i<<1]|sum[i<<1|1];
 12 }
 13 void build(int l,int r,int rt)///建树
 14 {
 15     if(l==r)
 16     {
 17         sum[rt]=0;
 18         return ;
 19     }
 20     int mid=(r+l)>>1;
 21     build(l,mid,rt<<1);
 22     build(mid+1,r,rt<<1|1);
 23     push_up(rt);
 24 }
 25 void add(int l,int r,int pos,int v,int rt)
 26 {
 27     if(l==r)
 28     {
 29         sum[rt]|=1ll<<(v-1);///1ll为长整型的1
 30         return ;
 31     }
 32     int m=(r+l)>>1;
 33     if(m>=pos)
 34     {
 35         add(l,m,pos,v,rt<<1);
 36     }
 37     else
 38     {
 39         add(m+1,r,pos,v,rt<<1|1);
 40     }
 41     push_up(rt);
 42 }
 43 void del(int l,int r,int pos,int val,int rt)
 44 {
 45     if(l==r)
 46     {
 47         sum[rt]&=~(1ll<<(val-1));
 48         return ;
 49     }
 50     int mid=(r+l)>>1;
 51     if(pos<=mid)
 52     {
 53         del(l,mid,pos,val,rt<<1);
 54     }
 55     else
 56     {
 57         del(mid+1,r,pos,val,rt<<1|1);
 58     }
 59     push_up(rt);
 60 }
 61 ll query(int l,int r,int L,int R,int rt)///区间查询
 62 {
 63     if(L<=l&&R>=r)
 64     {
 65         return sum[rt];
 66     }
 67     ll ans=0;
 68     int mid=(r+l)>>1;
 69     if(L<=mid)
 70     {
 71         ans|=query(l,mid,L,R,rt<<1);
 72     }
 73     if(R>mid)
 74     {
 75         ans|=query(mid+1,r,L,R,rt<<1|1);
 76     }
 77     return ans;
 78 }
 79
 80 int main()
 81 {
 82     int i,t;
 83     int op,x,y;
 84     scanf("%d",&t);
 85     for(i=1; i<=t; i++)
 86     {
 87         printf("Case %d:\n",i);
 88         scanf("%d%d",&n,&q);
 89         build(1,n,1);
 90         while(q--)
 91         {
 92             scanf("%d",&op);
 93             scanf("%d%d",&x,&y);
 94             if(op==1)
 95             {
 96                 add(1,n,x,y,1);
 97             }
 98             if(op==2)
 99             {
100                 del(1,n,x,y,1);
101             }
102             if(op==3)
103             {
104                 ll ans=query(1,n,x,y,1);
105                 ll cnt=0;
106                 int flag=1;
107                 int ot=1;
108                 while(ans)
109                 {
110                     if(ans&1)
111                     {
112                         if(flag)
113                         {
114                             flag=0;
115                         }
116                         else
117                         {
118                             printf(" ");
119                         }
120                         printf("%d",ot);
121                     }
122                     ot++;
123                     ans>>=1;
124                 }
125                 if(flag)
126                 {
127                     printf("%%\n");
128                 }
129                 else
130                 {
131                     printf("\n");
132                 }
133             }
134         }
135     }
136     return 0;
137 }

原文地址:https://www.cnblogs.com/wkfvawl/p/9557057.html

时间: 2024-08-29 22:13:24

皮卡丘的梦想2(线段树+二进制状态压缩)的相关文章

poj 2777 Count Color(线段树、状态压缩、位运算)

Count Color Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 38921   Accepted: 11696 Description Chosen Problem Solving and Program design as an optional course, you are required to solve all kinds of problems. Here, we get a new problem.

# 最短Hamilton路径(二进制状态压缩)

最短Hamilton路径(二进制状态压缩) 题目描述:n个点的带权无向图,从0-n-1,求从起点0到终点n-1的最短Hamilton路径(Hamilton路径:从0-n-1不重不漏的每个点恰好进过一次) 题解:二进制状态压缩算法\(O(2^n*n^2)\),需要记录当前经过了哪些点,当前在哪个位置.\(f[i][j]\) ? \(i\)转化为二进制每一位代表是否经过该点,\(j\)表示当前位于j这个点 #include <iostream> #include <cstring> u

二进制状态压缩及位运算

位运算符: 与(&),或(|),非(~), 异或(^); 移位运算: 1.左移:在二进制表示下把数字同时向左移动,低位以0填充,高位越界后舍弃 1 << n = 2^n, n << 1 = 2n 2.算术右移:在二进制补码表示下把数字同时向右移动,高位以符号位填充,低位越界后舍弃 n >> 1 = n/2.0(向下取整) eg:(-3)>> 1 = -2, 3 >> 1 = 1;  二进制状态压缩,是指将一个n位的 bool 数组用 n

POJ - 2777——Count Color(懒标记线段树二进制)

Count Color Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 53639   Accepted: 16153 Description Chosen Problem Solving and Program design as an optional course, you are required to solve all kinds of problems. Here, we get a new problem.

Sleep Buddies (二进制状态压缩)

Sleep Buddies 算法:状态压缩, 把每一个集合都压缩成一个数字. 使用方法:把每个状态都进行1<<(x-1)压缩,这样的话我们可以保证,每个二进制上代表的那个数字是1就代表存在这个属性. AC_Code 1 #include <bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int maxn=1e5+10; 5 #define rep(i,first,last) for(ll i

Count Color (线段树区间染色?二进制状态压缩)

题目链接:https://vjudge.net/problem/POJ-2777 题意: 有L个画板,30种颜色,o个操作:P a b :询问a-b 种有多少种颜色不同的,C  a b c:把a-b全部涂成c的颜色(覆盖掉) 1 #include <stdio.h> 2 #include <algorithm> 3 #include <iostream> 4 #include <cstring> 5 #include <string> 6 #in

二进制状态压缩相关操作

取出整数n在二进制表示下的第k位:(n>>k)&1 取出整数n在二进制表示下的第0~k-1位(后k位):n&((1<<k)-1) 把整数n在二进制表示下的第k位取反:n^(1<<k) 对整数n在二进制表示下的第k位赋值1:n|(1<<k) 对整数n在二进制表示下的第k位赋值0:n&(~(1<<k)) 原文地址:https://www.cnblogs.com/Mr94Kevin/p/10376623.html

POJ-3279.Fliptile(二进制状态压缩 + dfs) 子集生成

昨天晚上12点刷到的这个题,一开始一位是BFS,但是一直没有思路.后来推了一下发现只需要依次枚举第一行的所有翻转状态然后再对每个情况的其它田地翻转进行暴力dfs就可以,但是由于二进制压缩学的不是很透,一直有小问题,下面我还会讲子集生成的相关方法,有兴趣的同学可以继续关注. 本题大意:一块地,有黑(1)白(0)之分,牛每次踩踏使得当前块以及四连块都变色,问当牛如何踩时使得地都变白并且求出最小踩踏次数和踩踏路径的最小字典序时的踩踏地图. 本题思路:由于同一块地被翻两次都会回到原来的状态,所以只需要对

二进制状态压缩:应用及例题

1.位运算:https://www.cnblogs.com/yrjns/p/11246163.html 符号 描述 运算规则 & 与 两个位都为1时,结果才为1 | 或 两个位都为0时,结果才为0 ^ 异或 两个位相同为0,相异为1 ~ 取反 0变1,1变0 << 左移 各二进位全部左移若干位,高位丢弃,低位补0 >> 右移 各二进位全部右移若干位,对无符号数,高位补0,有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移) Tips: (1)&a