[NOI2017] 整数
一、题目
二、解法
首先要知道暴力为什么慢才能去优化,首先如果 \(a\) 只有正数的话那么修改的时间是均摊 \(O(n)\) 的,但是会有几部耗时很久,如果回撤的话就会非常耗时。这道题 \(a\) 是负数相当于回撤的效果,所以最坏情况是 \(O(n^2)\) 级别的。
由于只有加法是很快的,我们不妨把加法和减法分开维护,大概的思路是修改分别在加法数组和减法数组上面改,查询就用加法数组和减法数组减一下就行了。
因为 \(n\) 太大了所以还要用到压位的奇技淫巧,我们把 \(32\) 个字符压成一位,我们把压位后的这部分称为块,那么每次就找到对应的块修改,然后有进位暴力进上去即可,由于 \(a\leq 2^{30}\) 所以这部分时间复杂度 \(O(n)\)
然后就是查询了,这里要用到一个性质:加法数组的任意前缀一定比减法数组的对应前缀大,加入我们询问位置 \(k\) 是 \(0\) 还是 \(1\),首先仅考虑这个位置看是 \(0\) 还是 \(1\)(也就是先把前缀做减法),再判断后缀的大小,如果加法数组的后缀更大那么无需进位,答案就是仅考虑前缀的答案,如果减法数组的后缀更大那么需要进位,答案需要异或 \(1\)
那么怎么比较两个数组后缀的大小关系呢?其实就像比字典序那样就行了,首先我们比较 \(k\) 所在块内那零散的一点点的大小关系,然后找到后面第一个不同的位置,比较大小关系即可,那么可以用 \(set\) 维护出所有不同的位置,然后直接 \(\tt lower\_bound\) 即可,时间复杂度 \(O(n\log n)\)
#include <cstdio>
#include <set>
using namespace std;
#define ui unsigned int
const int M = 1000005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n;ui ad[M],sb[M];set<int> s;
signed main()
{
n=read();read();read();read();
while(n--)
{
int op=read();
if(op==1)
{
int a=read(),b=read(),p=b/32,q=b%32;
if(a>0)
{
ui d=(ui)a<<q,ic=(ui)a>>(31-q);ic>>=1;
ui tmp=ad[p];ad[p]+=d;ic+=(tmp>ad[p]);
if(ad[p]!=sb[p]) s.insert(p);else s.erase(p);
while(ic)
{
p++;
tmp=ad[p];ad[p]+=ic;ic=(tmp>ad[p]);
if(ad[p]!=sb[p]) s.insert(p);else s.erase(p);
}
}
else
{
a=-a;
ui d=(ui)a<<q,ic=(ui)a>>(31-q);ic>>=1;
ui tmp=sb[p];sb[p]+=d;ic+=(tmp>sb[p]);
if(ad[p]!=sb[p]) s.insert(p);else s.erase(p);
while(ic)
{
p++;
tmp=sb[p];sb[p]+=ic;ic=(tmp>sb[p]);
if(ad[p]!=sb[p]) s.insert(p);else s.erase(p);
}
}
}
else
{
int b=read(),p=b/32,q=b%32,ans=((ad[p]>>q)^(sb[p]>>q))&1;
ui x=ad[p]%(1ll<<q),y=sb[p]%(1ll<<q);
if(x<y) printf("%d\n",ans^1);
else if(x>y || s.empty() || p<=(*s.begin())) printf("%d\n",ans);
else
{
set<int>::iterator it=s.lower_bound(p);it--;
if(ad[*it]>sb[*it]) printf("%d\n",ans);
else printf("%d\n",ans^1);
}
}
}
}