版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_33229466/article/details/82260607
题意
有n个敌人,编号为0到n-1,每个敌人都有一个血量h。现在有q次操作,每次给出两个数x和y,表示将所有编号为x的子集(二进制下)的敌人血量都减去y。要求每次操作后输出还剩下多少个敌人的血量大于0。
分析
已经颓废到开始写题了。
首先可以整体二分(或者分块),然后就变成了子集加和单点查询问题,很容易想到分成前面9位和后面9位来做,这样复杂度就是
,然后极限爆过去了。
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
typedef long long LL;
const int N=500005;
const int par1=(1<<9)-1;
const int par2=(1<<18)-1-par1;
int n,m,t[N],a[N],b[N],bin[20],ans[N],die[N],tmp[N];
LL h[N],w[N];
int read()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void add(int x,LL y)
{
int p1=x&par1,p2=x&par2;
for (int s=p2;s>=0;s=s?((s-1)&p2):s-1) w[s+p1]+=y;
}
LL query(int x)
{
int p1=x&par1,p2=x&par2,p3=p1^par1;
LL ans=0;
for (int s=p3;s>=0;s=s?((s-1)&p3):s-1) ans+=w[s+p1+p2];
return ans;
}
void solve(int l,int r,int L,int R)
{
if (L>R) return;
if (l==r)
{
for (int i=L;i<=R;i++)
if ((t[i]&a[l])==t[i]&&b[l]>=h[t[i]]) die[t[i]]=l;
return;
}
int mid=(l+r)/2;
for (int i=l;i<=mid;i++) add(a[i],b[i]);
int p=L,q=R;
for (int i=L;i<=R;i++)
{
LL val=query(t[i]);
if (val>=h[t[i]]) tmp[p++]=t[i];
else h[t[i]]-=val,tmp[q--]=t[i];
}
for (int i=L;i<=R;i++) t[i]=tmp[i];
for (int i=l;i<=mid;i++) add(a[i],-b[i]);
solve(l,mid,L,p-1);solve(mid+1,r,q+1,R);
}
int main()
{
bin[0]=1;
for (int i=1;i<=18;i++) bin[i]=bin[i-1]*2;
n=read();
for (int i=0;i<n;i++) h[i]=read();
m=read();
for (int i=1;i<=m;i++) a[i]=read(),b[i]=read();
for (int i=1;i<=n;i++) t[i]=i-1;
solve(1,m,1,n);
for (int i=0;i<n;i++) ans[die[i]]--;
ans[1]+=n;
for (int i=2;i<=m;i++) ans[i]+=ans[i-1];
for (int i=1;i<=m;i++) printf("%d\n",ans[i]);
return 0;
}