洛谷P3822/LOJ2302/UOJ314/BZOJ4942[NOI2017]整数
首先要知道一个结论:已知一个二进制计数器,每次加一,暴力置位,均摊时间是$O(1)$的。
相似地,用会计分析或势能分析可以证明一个更强的结论:每次在计数器某一位上加一,复杂度仍为$O(1)$。所以,设数总长为$L$,若$a$始终不小于零,则我们得到一个$O(Lloga)$的方法。
但这道题要求支持减法(即回撤),这将使均摊失效,也就是单次加/减将是$O(L)$的。所以我们只能考虑把加减分开,保存$a$和$b$,加时在$a$上加,减时在$b$上加,查询时在$a-b$上查询。
那么考虑如何快速求出$a-b$的第k位。不难发现对第k位有影响的只有$a$,$b$的第k位和$a$,$b$更低位数的大小关系(决定是否借位),而大小关系由第一个不同位决定,可以考虑用set维护不同位,就可以做到$O(logL)$。
然而$L$可以达到$3\times 10^7$。。。所以用压位,$2^{64}$进制,unsigned long long自然溢出实现效率很高。此时每次加减可以视为只影响一位,复杂度为操作set的$O(logL)$,总复杂度$O(LlogL)$,由于$L$压位后小于$5\times 10^5$,效率很高。
#include<cstdio> #include<set> using namespace std; typedef unsigned long long ull; const int N=500050; char rB[1<<21],*rS,*rT,wB[1<<21]; int wp=-1; inline char gc(){return rS==rT&&(rT=(rS=rB)+fread(rB,1,1<<21,stdin),rS==rT)?EOF:*rS++;} inline void flush(){fwrite(wB,1,wp+1,stdout);wp=-1;} inline void pc(char c){if(wp+1==(1<<21))flush();wB[++wp]=c;} inline int rd(){ char c=gc(); bool f=0; for(;c<48||c>57;c=gc())if(c=='-')f=1; int x=c&15; for(c=gc();c>=48&&c<=57;c=gc())x=(x<<3)+(x<<1)+(c&15); return f?-x:x; } ull a[N],b[N]; set<int> S; inline void add(ull *a,ull *b,int x,int k){ int p=k>>6,pp=k&63; ull t=(ull)x>>63ull-pp,pre=a[p];t>>=1ull; a[p]+=(ull)x<<pp;if(a[p]<pre)++t; //若a[p]<pre,则说明进位发生 if(a[p]^b[p])S.insert(p); else if(S.find(p)!=S.end())S.erase(p); for(++p;t;++p){ pre=a[p];a[p]+=t;t=a[p]<pre; if(a[p]^b[p])S.insert(p); else if(S.find(p)!=S.end())S.erase(p); } } inline bool query(int k){ int p=k>>6,pp=k&63; bool ans=((a[p]>>pp)^(b[p]>>pp))&1ull; ull t1=a[p]&(1ull<<pp)-1ull,t2=b[p]&(1ull<<pp)-1ull; if(t1<t2)return ans^1; if(t1>t2||S.empty()||p<=*(S.begin()))return ans; set<int>::iterator it=S.lower_bound(p);--it; return ans^(a[*it]<b[*it]); } int main(){ int n=rd(),t,x,y; rd();rd();rd(); while(n--){ t=rd();x=rd(); if(t==1){ y=rd(); if(x>0)add(a,b,x,y); else if(x<0)add(b,a,-x,y); }else if(t==2){pc(query(x)?'1':'0');pc('\n');} } flush(); return 0; }