CF1651F Tower Defense

CF1651F Tower Defense

洛谷CF1651F Tower Defense

题目大意

n n n座防御塔按 1 1 1 n n n的顺序排成一列,每座防御塔都有一个能量上限 c i c_i ci和能量回复速率 r i r_i ri。对于一座塔 i i i,每过一秒,它的能量 w i w_i wi就会变成 min ⁡ ( w i + r i , c i ) \min(w_i+r_i,c_i) min(wi+ri,ci),每座塔在初始时满能量。

q q q个怪物,每个怪物都有两个属性 t i t_i ti h i h_i hi,表示这个怪物会在第 t i t_i ti秒出现在第一座塔前。当它到第 j j j座塔前时,自己的血量 h i h_i hi会减少 min ⁡ ( h i , w j ) \min(h_i,w_j) min(hi,wj),塔的能量也会减少这个数。当怪物的血量 h i = 0 h_i=0 hi=0时怪物停止移动,否则它下一秒会移动到下一座塔。

有些怪物在经过塔 n n n后血量仍未减少至 0 0 0,求这样的怪物最终剩下的血量的总和。

注意每座塔是先恢复能量再打怪物。

  • 1 ≤ n , q ≤ 2 × 1 0 5 1\leq n,q\leq 2\times 10^5 1n,q2×105
  • 1 ≤ c i , r i ≤ 1 0 9 1\leq c_i,r_i\leq 10^9 1ci,ri109
  • 1 ≤ h i ≤ 1 0 12 1\leq h_i\leq 10^{12} 1hi1012
  • 0 ≤ t i ≤ 2 × 1 0 5 0\leq t_i\leq 2\times 10^5 0ti2×105
  • ∀ 1 ≤ j < q , t j < t j + 1 \forall 1\leq j∀1j<q,tj<tj+1

题解

考虑对塔进行分块,当一个怪进入一个块内的塔时,只有两种情况:

  • 块内所有塔把怪打了一遍之后怪还有血,这时可以看作块中所有塔内的能量都被清零了
  • 怪被块中的一座塔打到血量为 0 0 0,此时这个怪不会再对后面的块产生贡献

我们发现,因为一个怪最多会被打空血一次,所以第二种情况只会出现 O ( q ) O(q) O(q)次。

对于每个块,维护一个推平标记 f l fl fl表示这个块是否被推平,以及 l s t lst lst表示这个块上次被操作的时间戳。预处理出每个块被清零后 t t t秒这个块的能量总和 p r t pr_t prt,对于每个怪依次处理每个块:

  • 如果这个块没被推平或怪能被块中的一座塔打空血,则暴力遍历一遍这个块,并判断这个怪是否能推平这个块
  • 否则,直接把这个怪的血量减去预处理出的值,推平标记不变

我们发现,一开始有 n n n个块没被推平,后面每个怪都会使得有最多一个块从被推平状态变为没被推平状态,也就是说第一种情况只会出现 O ( n + q ) O(n+q) O(n+q)次。每次 O ( n ) O(\sqrt n) O(n )暴力修改,均摊下来的时间复杂度为 O ( ( n + q ) n ) O((n+q)\sqrt n) O((n+q)n )

每个怪最多遍历 n \sqrt n n 个块,所以第二种情况的时间复杂度为 O ( q n ) O(q\sqrt n) O(qn )

下面考虑如何对每个块求 p r t pr_t prt。因为 1 ≤ t i ≤ 2 × 1 0 5 1\leq t_i\leq 2\times 10^5 1ti2×105,所以我们只需要求 1 ≤ t ≤ 2 × 1 0 5 1\leq t\leq 2\times 10^5 1t2×105 p r t pr_t prt即可。

设当前块在第 0 0 0时刻清零,当前时刻为 t t t,则对于块中的每一座塔 i i i,分为三种情况:

  • 1 ≤ t ≤ ⌊ c i r i ⌋ 1\leq t\leq \lfloor\dfrac{c_i}{r_i}\rfloor 1trici,则从 t − 1 t-1 t1 t t t这一单位时间,塔 i i i贡献了 r i r_i ri的能量
  • t = ⌊ c i r i ⌋ + 1 t=\lfloor\dfrac{c_i}{r_i}\rfloor+1 t=rici+1,则从 t − 1 t-1 t1 t t t这一单位时间,塔 i i i贡献了 c i   m o d   r i c_i\bmod r_i cimodri的能量
  • t ≥ ⌊ c i r i ⌋ + 2 t\geq \lfloor\dfrac{c_i}{r_i}\rfloor+2 trici+2,则从 t − 1 t-1 t1 t t t这一单位时间,塔 i i i没有贡献能量

我们考虑维护每一秒的能量增量的差分,做一次前缀和就能得到每一秒的能量增量,再做一次前缀和即可得到 p r t pr_t prt

这样每求一块的 p r t pr_t prt O ( n ) O(n) O(n)的,总共有 n \sqrt n n 块,所以预处理的时间复杂度为 O ( n n ) O(n\sqrt n) O(nn )

虽然时间复杂度是 O ( ( n + q ) n ) O((n+q)\sqrt n) O((n+q)n )的,但这题的空间复杂度是 O ( n n ) O(n\sqrt n) O(nn )的,数组开不下。我们考虑如何降低空间复杂度。

我们可以先把询问离线下来,然后一块一块地做,每块都将每个怪处理一次,这样就只需保留当前块的 p r t pr_t prt和推平标记,空间复杂度就降为 O ( n ) O(n) O(n)了。

总时间复杂度为 O ( ( n + q ) n ) O((n+q)\sqrt n) O((n+q)n ),空间复杂度为 O ( n ) O(n) O(n)

code

#include
using namespace std;
const int N=200000;
int n,q,bl;
long long ans=0,C[N+5],R[N+5],t[N+5],h[N+5],w[N+5],pr[N+5],v[N+5];
int pos(int i){
	return (i-1)/bl+1;
}
void init(int l,int r){
	long long wt=0;
	for(int i=0;i<=N;i++) v[i]=0;
	for(int i=l;i<=r;i++){
		wt+=R[i];
		if(C[i]/R[i]>=N) continue;
		v[C[i]/R[i]+1]+=C[i]%R[i]-R[i];
		v[C[i]/R[i]+2]+=-C[i]%R[i];
	}
	for(int i=1;i<=N;i++){
		wt+=v[i];
		pr[i]=pr[i-1]+wt;
	}
}
void solve(int l,int r){
	init(l,r);
	int fl=0,lst=0;
	for(int i=1;i<=q;i++){
		if(!h[i]) continue;
		if(!fl||fl&&h[i]<=pr[t[i]-t[lst]]){
			fl=1;
			for(int j=l;j<=r;j++){
				w[j]=min(C[j],w[j]+(t[i]-t[lst])*R[j]);
				if(h[i]>=w[j]){
					h[i]-=w[j];w[j]=0;
				}
				else{
					w[j]-=h[i];h[i]=0;fl=0;
				}
			}
		}
		else h[i]-=pr[t[i]-t[lst]];
		lst=i;
	}
}
int main()
{
//	freopen("dinosaurs.in","r",stdin);
//	freopen("dinosaurs.out","w",stdout);
	scanf("%d",&n);
	bl=sqrt(n);
	for(int i=1;i<=n;i++){
		scanf("%lld%lld",&C[i],&R[i]);
		w[i]=C[i];
	}
	scanf("%d",&q);
	for(int i=1;i<=q;i++){
		scanf("%lld%lld",&t[i],&h[i]);
	}
	for(int i=1;i<=pos(n);i++){
		solve(i*bl-bl+1,min(i*bl,n));
	}
	for(int i=1;i<=q;i++) ans+=h[i];
	printf("%lld",ans);
	return 0;
}

你可能感兴趣的:(题解,好题,题解,c++)