CF1651F Tower Defense 题解
CF1651F Tower Defence
其实是一道分块题,但是我不想写分块,于是就写了主席树。
暴力是显然的,每一个怪物出现时都让它把塔挨个走一遍即可。现在要优化这个暴力,就要考虑如何快速处理每个怪物的情况。
发现一个怪物会让一段塔的前缀的魔力值变成 \(0\),然后死在一个塔上并让这个塔的魔力值减小一部分,后面的塔不动。那么如果我们把这一段变为 \(0\) 的前缀划分为一个段,每次模拟怪物走过的路时按照段来模拟,不难发现段数是 \(O(n)\) 的,复杂度就是正确的。
现在问题主要在于如何正确维护一段中每个塔当前的魔力值,毕竟每个塔的上限和回复值都是不一样的。这里有一道引子题:CF837G,大体题意为给定 \(n\) 个一次分段函数 \(f_i(x)\),求其中一段函数值的和。做法是以值域为范围、开 \(n\) 棵主席树,每棵主席树维护当前分段函数在一定值域的函数值,即维护常数项和一次项系数,然后在主席树上就能方便地查询一段函数值的和了。那么这道题中,塔的魔力值同样可以看作一个分段函数 \(f_i(x)=\begin{cases}r_ix&x\le\lfloor\frac{c_i}{r_i}\rfloor\\c_i&x>\lfloor\frac{c_i}{r_i}\rfloor\end{cases}\),那么我们要求一段函数的和,同样能套用 CF837G 的方法。
然后这道题就差不多了,我们用栈来维护所有段,对于每个怪物挨个遍历所有段直到它死。如果当前段长度等于 \(1\),显然好维护;如果当前段的长度大于 \(1\),就二分找到怪物死的位置,把这个单点维护一下剩余魔力值,之前的塔全部合并成魔力值为 \(0\) 的一个段即可。
细节不少。最后是因为各种调试而码风极其混乱的代码:
#include<bits/stdc++.h>
#define int long long
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
using namespace std;
char buf[1<<20],*p1=buf,*p2=buf;
int read(){
int x=0;
char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x;
}
constexpr int MAXN=2e5+5;
int n,q,c[MAXN],r[MAXN];
int rt[MAXN];
int top;
struct{
int id,nc,lst;
}stk[MAXN];
int now,P;
struct{
#define lp st[p].lc
#define rp st[p].rc
#define lq st[q].lc
#define rq st[q].rc
struct SegTree{
int lc,rc;
int a,b;
}st[MAXN<<6];
int tot;
void chg(int x,int ka,int kb,int s,int t,int q,int&p){
st[p=++tot]=st[q];
st[p].a+=ka,st[p].b+=kb;
if(s==t) return;
int mid=(s+t)>>1;
if(x<=mid) chg(x,ka,kb,s,mid,lq,lp);
else chg(x,ka,kb,mid+1,t,rq,rp);
}
int ask(int l,int r,int x,int s,int t,int q,int p){
if(l<=s&&t<=r) return (st[p].a-st[q].a)*x+st[p].b-st[q].b;
int mid=(s+t)>>1,res=0;
if(l<=mid) res+=ask(l,r,x,s,mid,lq,lp);
if(mid<r) res+=ask(l,r,x,mid+1,t,rq,rp);
return res;
}
}T;
signed main(){
n=read();
for(int i=1;i<=n;i++){
c[i]=read(),r[i]=read();
T.chg(0,r[i],0,0,2e5+1,rt[i-1],rt[i]);
if(c[i]/r[i]<=2e5) T.chg(c[i]/r[i]+1,-r[i],c[i],0,2e5+1,rt[i],rt[i]);
}
q=read();
stk[0]={n+1,0,0};
for(int i=n;i;i--) stk[++top]={i,c[i],0};
int ans=0;
while(q--){
int t=read(),h=read();
while(top){
int L=stk[top].id,R=stk[top-1].id-1,dt=t-stk[top].lst;
if(L==R){
int tp=min(c[L],dt*r[L]+stk[top].nc);
if(tp>h){
stk[top].nc=tp-h;
stk[top].lst=t;
break;
}else h-=tp,top--;
}else{
int fk=T.ask(0,min(dt,200001ll),dt,0,2e5+1,rt[L-1],rt[R]);
if(fk>h){
int P=L-1;
int l=L,r=R;
while(l<=r){
int mid=(l+r)>>1;
if(T.ask(0,min(dt,200001ll),dt,0,2e5+1,rt[L-1],rt[mid])<=h) P=mid,l=mid+1;
else r=mid-1;
}
int now=T.ask(0,min(dt,200001ll),dt,0,2e5+1,rt[L-1],rt[P]);
P++;
if(P==R) top--;
else stk[top].id=P+1;
h-=now;
int tp=min(c[P],::r[P]*dt);
stk[++top]={P,tp-h,t};
break;
}else h-=fk,top--;
}
}
if(!top) ans+=h;
if(stk[top].id!=1) stk[++top]={1,0,t};
}
printf("%lld\n",ans);
return 0;
}
即得易见平凡,仿照上例显然。留作习题答案略,读者自证不难。
反之亦然同理,推论自然成立。略去过程 $\rm QED$,由上可知证毕。

浙公网安备 33010602011771号