P7424 [THUPC2017] 天天爱射击
【题意】
给定n个木板的在x轴上的位置,以及每个木板能经受的了几次射击,还有m颗子弹的射击位置(沿y轴方向),问每次射击后共有几个木板坏了
【分析】
我们考虑每块木板会在什么时候坏,较为暴力的考虑可以对于每个木板进行一次二分,时间复杂度$O(n^2logn)$
继续考虑,如果对子弹按照坐标排序,那么我们对于每个木板(x1,x2)可以找到对应的会射到这块木板的子弹区间[l,r],然后维护一个主席树可以做到$O(nlogn)$,但是由于常数较大,被整体二分吊打
下面介绍整体二分做法:
solve(l,r,L,R)表示现在子弹区间为[l,r],对应的木板(询问)为[L,R]
然后我们把l-mid的修改加入到树状数组中,记录坐标为1-i 的点会被射到几次
然后对于区间L-R的询问依次走一遍,把被射穿的分到左侧,没有射穿的减去被射击次数,分到右侧
注意这里要重新把树状数组减下去,方便后续操作
【代码】
#include<bits/stdc++.h> using namespace std; const int maxn=2e5+5; int n,m; struct board { int l,r,s,id; }b[maxn],b1[maxn],b2[maxn]; int a[maxn],ans[maxn],pos[maxn]; int c[maxn],maxi; int lowbit(int x) { return x&(-x); } void add(int x,int y) { for(int i=x;i<=maxi;i+=lowbit(i)) c[i]+=y; } int query(int x) { int res=0; for(int i=x;i>0;i-=lowbit(i)) res+=c[i]; return res; } void solve(int l,int r,int L,int R) { if(L>R) return; if(l==r) { for(int i=L;i<=R;i++) pos[b[i].id]=l; return; } int mid=(l+r)>>1; for(int i=l;i<=mid;i++) add(a[i],1); int cnt1=0,cnt2=0; for(int i=L;i<=R;i++) { int k=query(b[i].r)-query(b[i].l-1); if(k>=b[i].s) b1[++cnt1]=b[i]; else b[i].s-=k,b2[++cnt2]=b[i]; } for(int i=1;i<=cnt1;i++) b[i+L-1]=b1[i]; for(int i=1;i<=cnt2;i++) b[i+L+cnt1-1]=b2[i]; for(int i=l;i<=mid;i++) add(a[i],-1); solve(l,mid,L,L+cnt1-1); solve(mid+1,r,L+cnt1,R); } int main() { freopen("a.in","r",stdin); freopen("a.out","w",stdout); scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { scanf("%d%d%d",&b[i].l,&b[i].r,&b[i].s); b[i].id=i; maxi=max(maxi,b[i].r); } for(int i=1;i<=m;i++) scanf("%d",&a[i]); solve(1,m+1,1,n); for(int i=1;i<=n;i++) ans[pos[i]]++; for(int i=1;i<=m;i++) printf("%d\n",ans[i]); return 0; }