• 2.24 T1 P3515 [POI2011]Lightning Conductor


    思路:

    首先考虑n^2暴力枚举:ans[i]=max(a[j]+sqrt(|i-j|)-a[i] (1<=j<=n)

    把绝对值拆开:
    ans[i]=max(a[j]+sqrt(i-j))-a[i] (1<=j<=i)

    ans[i]=max(a[j]+sqrt(j-i))-a[i] (i<j<=n)

    发现这个式子具有对称性,也就是说对于一个点i,他的最优决策j有可能是在j的左边,也有可能是在j的右边,这是绝对值带来的影响,所以我们从前往后扫一遍,从后往前扫一便,就可以消除abs的影响

    对于函数y=sqrt(x),他的增长速度是越来越慢的,我们先考虑从前往后的情况,如果决策点j不变的情况下,i不断增大,那么sqrt(i-j)的增长速度就是不断减小的

    我们的dp方程求得是最大值

    如果i不断增大,决策点j产生的dp值的增幅不断减小(但是仍在增长),那就有可能存在一个决策点,虽然他之前的贡献比较小,但是增幅大,所以k有可能取代j形成dp[i]的最优解

    那么对于每一个决策点j,就可能形成最优解的范围应当是一段区间

    这样,我们的一个dp优化思路就是开一个单调队列,保存三元组(p,l,r),表示决策p的管辖范围是(l,r)内,(所有元素的l ~ r构成1 ~ n)我们枚举到一个i的时候,队头的l++,如果管辖范围为空(i不在head的管辖范围之内),那么就可以删去

    此时对于dp[i]而言,队头存储的就是最优决策点

    如果此时对于dp[n]而言,决策点p并没有i优,那么我们就是要在队尾加入决策点i

    i的管辖范围就是i~n

    因为增长速度是存在单调性的,也就是说如果在某一段时间p没有i优,那么p就一直没有i优,所以我们可以二分临界

    #include<iostream>
    #include<cstdlib>
    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #define il inline
    #define rg register
    #define ll long long
    #define N 540000
    #define inf 2147483647
    #define ll_inf 9223372036854775807
    using namespace std;
    int n,num[N];
    double F[N],G[N];
    struct T{
    	int p,l,r;
    }Q[N];
    il void re(rg int &x);
    double sol(rg int i,rg int j){
    	return (double)num[j]+sqrt((double)(1.0*abs(i-j)));
    }
    int find(rg int le,rg int ri,rg int p,rg int i){
    	rg int ans=-1;
    	while(le<=ri){
    		rg int mid=((le+ri)>>1);
    		if(sol(mid,p)>sol(mid,i))le=mid+1;
    		else ri=mid-1,ans=mid;
    	}
    	return ans;
    }
    int main(){
    	freopen("s.in","r",stdin);
    	re(n);
    	for(rg int i=1;i<=n;++i)
    		re(num[i]);
    	//dp[i]=max(num[j]-num[i]+sqrt(abs(i-j)));
        //需要求出最优的解
    	rg int tail=0,head=1;
    	for(rg int i=1;i<=n;++i){
    		Q[head].l++;
    		if(head<=tail&&Q[head].r<Q[head].l)head++;
    		if(head>tail||sol(n,i)>sol(n,Q[tail].p)){
    			while(head<=tail&&sol(Q[tail].l,Q[tail].p)<sol(Q[tail].l,i))tail--;
    			if(head>tail)
    				Q[++tail].p=i,Q[tail].l=i,Q[tail].r=n;
    			else{
    				rg int temp=find(Q[tail].l,Q[tail].r,Q[tail].p,i);
    				Q[tail].r=temp-1;
    				Q[++tail].p=i,Q[tail].l=temp,Q[tail].r=n;
    			}
    		}
    		F[i]=sol(i,Q[head].p)-num[i];
    	}
    	reverse(num+1,num+n+1);
    	tail=0,head=1;
    	for(rg int i=1;i<=n;++i){
    		Q[head].l++;
    		if(head<=tail&&Q[head].r<Q[head].l)head++;
    		if(head>tail||sol(n,i)>sol(n,Q[tail].p)){
    			while(head<=tail&&sol(Q[tail].l,Q[tail].p)<sol(Q[tail].l,i))tail--;
    			if(head>tail)
    				Q[++tail].p=i,Q[tail].l=i,Q[tail].r=n;
    			else{
    				rg int temp=find(Q[tail].l,Q[tail].r,Q[tail].p,i);
    				Q[tail].r=temp-1;
    				Q[++tail].p=i,Q[tail].l=temp,Q[tail].r=n;
    			}
    		}
    		G[i]=sol(i,Q[head].p)-num[i];
    	}
    	reverse(G+1,G+n+1);
    	for(rg int i=1;i<=n;++i)
    		printf("%d
    ",max(0,(int)ceil(max(F[i],G[i]))));
        return 0;
    }
    il void re(rg int &x){
        rg int res=0;rg int w=1;char c=getchar();
        while((c<'0'||c>'9')&&c!='-')c=getchar();
        if(c=='-')w=-1,c=getchar();
        while(c>='0'&&c<='9')res=(res<<3)+(res<<1)+c-'0',c=getchar();
        x=w*res;
    }
    

    这里还有另一个思路,不过设置的是四元组

    还是正着扫一遍,倒着扫一遍,但是我们在for循环时候的变量名变了: L,R,l,r表示f[L] ~ f[R]区间中所有点的最优策略在l~r这个区间内,我们二分查找,每次查找L,R的中点mid,对于mid来说,mid的最优解也一定在l ~ r范围之内,所以我们直接在l ~ min(mid,r)的范围内暴力枚举答案即可,为什么边界是min(mid,r)呢?因为我们正序枚举的时候是强制规定f[i]的最优解一定在f[i]的左边,如果最优解在f[i]的右边,那么我们直接在倒序枚举的时候处理就可以,故只枚举min(l,r)即可。

    代码:

    #include<bits/stdc++.h>
    #define ll long long
    #define maxn 500000+7
    using namespace std;
    
    inline ll read()//快读 
    {
        char c=getchar();
        ll x=0,f=1;
        while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}//不需要判负删掉来简化 
        while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
        return x*f;
    }
    
    ll n;
    ll a[maxn];
    long double zheng_f[maxn],dao_f[maxn];
    /*
    void solve1(int L,int R,int l,int r)
    {
    	if (L>R) return;
    	int mid=(L+R)>>1,g(0);
    	long double tmp(0);
    	zheng_f[mid]=a[mid];
    	for (int i=l;i<=min(r,mid);i++)
    	{
    		tmp=a[i]+sqrt(double(mid-i));
    		if (tmp>zheng_f[mid]) zheng_f[mid]=tmp,g=i;
    	}
    	if (g==0) g=mid; zheng_f[mid]-=a[mid];
    	solve1(L,mid-1,l,g); 
    	solve1(mid+1,R,g,r);
    }*/
    void work_zheng(ll L,ll R,ll l,ll r)
    {
    	if(L>R) return ;
    	ll mid=(L+R)>>1,k=0;
    	zheng_f[mid]=a[mid];
    	ll q=min(mid,r);
    	for(int i=l;i<=q;i++)
    	{
    		long double tmp=a[i]+sqrt(double(abs(mid-i)));
    		if(tmp>zheng_f[mid]) zheng_f[mid]=tmp,k=i; 
    	}
    	
    	if(k==0) k=mid;zheng_f[mid]-=a[mid]; 
    	work_zheng(L,mid-1,l,k);
    	work_zheng(mid+1,R,k,r);
    }
    
    void work_dao(ll L,ll R,ll l,ll r)
    {
    	if(L>R) return;
    	ll mid=(L+R)>>1,k=0;
    	dao_f[mid]=a[mid];
    	ll q=max(mid,l);
    	for(int i=r;i>=q;i--)
    	{
    		long double tmp=a[i]+sqrt(double(abs(mid-i)));
    		if(tmp>dao_f[mid]) dao_f[mid]=tmp,k=i;
    	}
    	
    	if(k==0) k=mid;dao_f[mid]-=a[mid];
    	work_dao(L,mid-1,l,k);
    	work_dao(mid+1,R,k,r);
    } 
    /*
    void solve2(int L,int R,int l,int r)
    {
    	if (L>R) return ;
    	int mid=(L+R)>>1,g(0);
    	long double tmp(0);
    	dao_f[mid]=a[mid];
    	for (int i=r;i>=max(mid,l);i--)
    	{
    		tmp=a[i]+sqrt(double(i-mid));
    		if (tmp>dao_f[mid]) dao_f[mid]=tmp,g=i;
    	}
    	if (g==0) g=mid; dao_f[mid]-=a[mid];
    	solve2(L,mid-1,l,g);
    	solve2(mid+1,R,g,r);
    }
    */
    
    int main()
    {
    	freopen("thunder.in","r",stdin);
    	freopen("thunder.out","w",stdout);
    	n=read();
    	for(int i=1;i<=n;i++) a[i]=read();
    	
    	/*//预处理出正序循环时第一个数的p
    	ll zheng_p=0;
    	double tmp=0;
    	ll jj=0;
    	for(int j=1;j<=n;j++)
    	{
    		tmp=sqrt(abs(1-j));
    		if((int)tmp!=tmp)//如果不是整数 
    		tmp=(int)tmp+1;
    		
    		if(zheng_p<a[j]-a[1]+tmp) 
    		{
    			zheng_p=max(zheng_p,a[j]-a[1]+(int)tmp);
    			jj=j;
    		}
    		
    	}
    	zheng_f[1]=zheng_p;
    	//正序处理
    	tmp=0; 
    	double tmp2=0;
    	for(int i=2;i<=n;i++)
    	{
    		tmp=sqrt(abs(jj-(i-1)));
    		tmp2=sqrt(abs(jj-i));
    		if((int)tmp!=tmp) tmp=(int)tmp+1;
    		if((int)tmp2!=tmp2) tmp2=(int)tmp2+1;
    		zheng_f[i]=max(zheng_f[i],zheng_f[i-1]-(int)tmp+a[i-1]-a[i]+(int)tmp2);
    	} 
    	
    	
    	
    	//预处理出倒序循环时第一个数的p 
    	ll dao_p=0;
    	tmp=0;
    	jj=0;
    	for(int j=n;j>=1;j--)
    	{
    		tmp=sqrt(abs(n-j));
    		if((int)tmp!=tmp)//如果不是整数 
    		tmp=(int)tmp+1; 
    		if(dao_p<a[j]-a[n]+tmp)
    		{
    			dao_p=max(dao_p,a[j]-a[n]+(int)tmp);
    			jj=j;
    		}	
    	}
    	
    	dao_f[n]=dao_p;
    	
    	//倒序处理
    	for(int i=n-1;i>=1;i--)
    	{
    		tmp=sqrt(abs(jj-(i+1)));
    		tmp2=sqrt(abs(jj-i));
    		if((int)tmp!=tmp) tmp=(int)tmp+1;
    		if((int)tmp2!=tmp2) tmp2=(int)tmp2+1;
    		dao_f[i]=max(dao_f[i],a[jj]-a[i]+(int)tmp2);
    	} 
    	
    	for(int i=1;i<=n;i++)
    	{
    		cout<<max(zheng_f[i],dao_f[i])<<"
    ";
    	}
    	
    	/*cout<<'
    ';
    	for(int i=1;i<=n;i++) cout<<zheng_f[i]<<" ";
    	cout<<'
    ';
    	for(int i=1;i<=n;i++) cout<<dao_f[i]<<" ";
    	*/
    	
    	work_zheng(1,n,1,n);
    	work_dao(1,n,1,n);
    //solve1(1,n,1,n);
    //solve2(1,n,1,n);
    //cout<<zheng_f[1]<<" "<<dao_f[1]<<'
    ';
    	for(int i=1;i<=n;i++) cout<<(ll)ceil(max(zheng_f[i],dao_f[i]))<<'
    ';
    	return 0;
    	
    }
    
  • 相关阅读:
    Go学习笔记(四)Go自动化测试框架
    VSCode快捷键
    Go学习笔记(三)Go语言学习
    Go学习笔记(二)搭建Visual Studio Code调试环境
    Go学习笔记(一)安装Go语言环境
    C# HTTPServer和OrleansClient结合
    C# Post HTTP Request
    Unity3D UGUI Shader画一个圆环
    Orleans学习总结(六)--应用篇
    Orleans学习总结(四)--集群配置篇
  • 原文地址:https://www.cnblogs.com/yxr001002/p/14444050.html
Copyright © 2020-2023  润新知