• CF1651F Tower Defense


    一、题目

    点此看题

    二、解法

    考虑颜色段均摊,维护场上的若干个连续段 \([l,r]\),可以按照左端点降序排列,这样询问时类似弹栈做就行了。

    如果遇到 \(l=r\),这代表了一个单点,可以直接暴力计算。遇到 \(l<r\)这代表被以前的询问推平的一段区间,问题可以转化成给定初始生命 \(hp\),给定时间差 \(T\),给定左右端点 \([l,r]\),问是否能再次推平这个区间,或者是在这个区间停下。

    关键在于在某个时间差 \(T\) 下计算 \([l,r]\) 之间的权值和。每个塔关于时间的权值可以看成一个分段函数,当 \(T\leq \lfloor\frac{c}{r}\rfloor\) 是斜率为 \(r\),截距为 \(0\) 的一次函数;当 \(T>\lfloor\frac{c}{r}\rfloor\) 是一个截距为 \(c\) 的常函数。

    由于 \(t_i\leq 2\cdot 10^5\),我们可以预处理以位置为下标的可持久化线段树,这样每个塔可以转化为两个单点修改,线段树上维护一次函数即可,合并就是直接加。

    那么原来的问题可以用线段树上二分来解决,时间复杂度 \(O(n\log n)\)

    #include <cstdio>
    #include <vector>
    #include <iostream>
    using namespace std;
    const int M = 200005;
    const int N = 30*M;
    #define int long long
    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,m,q,T,p,dt[M],c[M],s[M],tim[M],nc[M];
    int ans,cnt,rt[M],k[N],b[N],ls[N],rs[N];
    vector<int> v[M];
    void build(int &x,int l,int r)
    {
    	x=++cnt;
    	if(l==r) {k[x]=dt[l];b[x]=0;return ;}
    	int mid=(l+r)>>1;
    	build(ls[x],l,mid);
    	build(rs[x],mid+1,r);
    	k[x]=k[ls[x]]+k[rs[x]];
    }
    void ins(int &x,int y,int l,int r,int p)
    {
    	x=++cnt;k[x]=k[y];b[x]=b[y];
    	ls[x]=ls[y];rs[x]=rs[y];
    	if(l==r) {k[x]=0;b[x]=c[l];return ;}
    	int mid=(l+r)>>1;
    	if(mid>=p) ins(ls[x],ls[y],l,mid,p);
    	else ins(rs[x],rs[y],mid+1,r,p);
    	k[x]=k[ls[x]]+k[rs[x]];
    	b[x]=b[ls[x]]+b[rs[x]];
    }
    int ask(int x,int l,int r,int L,int R)
    {
    	if(!x || l>R || L>r) return 0;
    	if(L<=l && r<=R) return T*k[x]+b[x];
    	int mid=(l+r)>>1;
    	return ask(ls[x],l,mid,L,R)+ask(rs[x],mid+1,r,L,R);
    }
    void find(int x,int l,int r,int L,int &hp)
    {
    	int mid=(l+r)>>1;
    	if(L<=l)
    	{
    		int w=T*k[x]+b[x];
    		if(w<=hp) {p=r;hp-=w;return ;}
    		if(l==r) return ;
    		if(find(ls[x],l,mid,L,hp),p==mid)
    			find(rs[x],mid+1,r,L,hp);
    	}
    	else if(mid<L) find(rs[x],mid+1,r,L,hp);
    	else if(find(ls[x],l,mid,L,hp),p==mid)
    		find(rs[x],mid+1,r,L,hp);
    }
    signed main()
    {
    	n=read();
    	for(int i=1;i<=n;i++)
    	{
    		c[i]=read();dt[i]=read();int j=c[i]/dt[i]+1;
    		if(j<M) v[j].push_back(i);
    	}
    	build(rt[0],1,n);
    	for(int i=1;i<M;i++)
    	{
    		rt[i]=rt[i-1];
    		for(int x:v[i]) ins(rt[i],rt[i],1,n,x);
    	}
    	q=read();s[0]=n+1;
    	for(int i=n;i>=1;i--) s[++m]=i,nc[m]=c[i];
    	while(q--)
    	{
    		int t=read(),h=read();
    		while(m)
    		{
    			int i=s[m],j=0;T=t-tim[m];
    			if(s[m-1]-i==1)//l==r
    			{
    				j=min(c[i],dt[i]*T+nc[m]);
    				if(j>h) {nc[m]=j-h;tim[m]=t;break;}
    				m--;h-=j;continue;
    			}
    			j=ask(rt[T],1,n,i,s[m-1]-1);
    			if(j>h)
    			{
    				p=i-1;find(rt[T],1,n,i,h);p++;
    				if(p==s[m-1]-1) m--;else s[m]=p+1;
    				s[++m]=p;tim[m]=t;
    				nc[m]=min(c[p],dt[p]*T)-h;break;
    			}
    			h-=j;m--;
    		}
    		if(!m) ans+=h;
    		if(s[m]!=1) s[++m]=1,tim[m]=t,nc[m]=0;
    	}
    	printf("%lld\n",ans);
    }
    
  • 相关阅读:
    使用Graphics合成带二维码和头像的分享图(小程序分享、App分享)
    04_关键字的作用
    03_线程
    02_进程
    01_命名规范
    WebApi的创建,部署,Oauth身份认证(三)
    WebApi的创建,部署,Oauth身份认证(二)
    WebApi的创建,部署,Oauth身份认证(一)
    Prism.Interactivity 和 Prism.Modularity 介绍
    Prism BindableBase 和 Commands 的介绍
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/16455138.html
Copyright © 2020-2023  润新知