大致题意: 给定一个整数(x)(初始为(0)),支持两种操作:给(x)加上(a imes 2^b);询问(x)二进制下第(k)位的值。
压位
遇到这种题应该自然而然就会想到压位吧。
一开始我以为(a)只有正数(日常眼瞎),那么实际上每次只要暴力加、暴力进位就可以了。
这种做法的复杂度我能口胡它是均摊(O(1))的,但由于我的证明方式比较难以描述,无法用书面文字表达,因此略了。
然而,由于(a)可能存在负值,那么只要开局加一个(2^{3 imes 10^7}),然后重复执行(-1,+1,-1,+1,...),相当于每次都要跑满,直接升天。
想了好久都没想出来该怎么修改这个做法,最终只好默默点开了题解。。。
结果发现我的做法其实已经非常接近正解了。
实际上,防止出现上述情况的最好方式,就是分别记录加上的数的总和和减去的数的总和,那么复杂度依然有保证。
接着考虑询问,我们先求出加与减的总和第(k)位的差值,然后只需判断(0sim k-1)位是否存在减法过程中出现借位即可。
判断出现借位,一位一位枚举过去显然是不现实的。而一个较为优秀的方法,就是找到最高的不同位,比较两个总和中这一位的大小。
那么如何找到最高的不同位呢?
其实,我们可以开一个(set),然后只要把所有两个总和不同的一段压位值的编号扔入(set)中,每次使用(lower\_bound)即可找到最高的不同位。
这道题就这样做完了。
嗲吗
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 1000000
#define U unsigned int
using namespace std;
U a[N+5],b[N+5];set<int> S;
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define D isdigit(c=tc())
int f;char c,*A,*B,FI[FS];
public:
I FastIO() {A=B=FI;}
Tp I void read(Ty& x) {x=0,f=1;W(!D) f=c^'-'?1:-1;W(x=(x<<3)+(x<<1)+(c&15),D);x*=f;}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
}F;
I void Add(U *s,U *k,Con U& x,CI y)
{
#define F5(x) (s[x]^k[x]?(S.insert(x),0):S.count(x)&&(S.erase(x),0))
RI p=y>>5,q=y&31;U t=s[p];s[p]+=x<<q,F5(p);//先处理当前位
U w=((x>>1)>>(31-q))+(s[p++]<t);W(w) t=s[p],s[p]+=w,F5(p),w=s[p++]<t;//进位
}
I int Qry(CI x)
{
RI p=x>>5,q=x&31,t=((a[p]^b[p])>>q)&1;//先求出第k位的差值
if((a[p]^b[p])&((1<<q)-1)) return t^((a[p]&((1<<q)-1))<(b[p]&((1<<q)-1)));//如果这一段压位值中已经存在不同
set<int>::iterator it=S.lower_bound(p);if(it==S.begin()) return t;//lower_bound找出最高的不同位
return --it,t^(a[*it]<b[*it]);//比较
}
int main()
{
RI Qt,i,op,x,y;F.read(Qt,op,x,y);W(Qt--)
F.read(op,x),op==1?F.read(y),x>0&&(Add(a,b,x,y),0),x<0&&(Add(b,a,-x,y),0)//修改
:(putchar(48|Qry(x)),putchar('
'));//询问
return 0;
}