zoukankan      html  css  js  c++  java
  • 51nod 1564 区间的价值 暴力

    题目链接

    Description
    我们定义“区间的价值”为一段区间的最大值 * 最小值。
    一个区间左端点在L,右端点在R,那么该区间的长度为(R-L+1)。
    现在聪明的杰西想要知道,对于长度为k的区间,最大价值的区间价值是多少。
    当然,由于这个问题过于简单。
    我们肯定得加强一下。
    我们想要知道的是,对于长度为1~n的区间,最大价值的区间价值分别是多少。
    样例解释:
    长度为1的最优区间为2-2 答案为6 * 6
    长度为2的最优区间为4-5 答案为4 * 4
    长度为3的最优区间为2-4 答案为2 * 6
    长度为4的最优区间为2-5 答案为2 * 6
    长度为5的最优区间为1-5 答案为1 * 6
    Input
    单组测试数据
    第一行一个数n(1<=n<=100000)。
    第二行n个正整数(1<=ai<=10^9),下标从1开始。
    由于某种不可抗力,ai的值将会是1~10^9内随机的一个数。(除了样例)
    Output
    输出共n行,第i行表示区间长度为i的区间中最大的区间价值。
    Input示例
    5
    1 6 2 4 4
    Output示例

    36
    16
    12
    12
    6

    题解:
    定义区间价值为区间最小值与最大值的乘积,求长度为1,2,…,n的区间最大值。n<=100,000,保证序列随机。

    当前长度为k区间,[l,r]最大值最小值对应覆盖一个区间,当r到达下一个区间时最大值或者最小值就会变化,但是不会变的太多,因为数据随机。所以预处理ST表快速查询区间最大值最小值的下标,预处理每个位置i后面第一个比他大maxn[i]和比他小的下标minn[i]。对于当前区间[l,r]最大值下标为a,最小值下标为b,下一次的右端点就是min{maxn[a],minn[b],a+k,b+k}。
    然后就跑过了。原因不明,复杂度无法证明。

    code

    #include<stdio.h>
    #include<algorithm>
    using namespace std;
    typedef long long LL;
    void Read(int& x){
    	char ch; while(ch=getchar(),ch<'0'||ch>'9');
    	x=ch-'0'; while(ch=getchar(),ch>='0'&&ch<='9') x=x*10+ch-'0';
    }
    int n,cnt;
    int A[100010],D[100010];
    struct Node{
    	int val,id;
    	bool operator < (const Node& a)const{return val<a.val;}
    }B[100010];
    int Minn[100010][19],Maxn[100010][19];
    int minn[100010],maxn[100010];
    int Tree1[100010],Tree2[100010],Table[100010];
    const int INF=0x7f7f7f7f;
    void Insert(int i,int x){
    	for(int j=i;j<=cnt;j+=(j&(-j))) Tree1[j]=min(Tree1[j],x);
    	for(int j=cnt-i+1;j<=cnt;j+=(j&(-j))) Tree2[j]=min(Tree2[j],x);
    }
    int QueryMax(int i){
    	int c=INF; for(i=cnt-i+1;i;i-=(i&(-i))) c=min(c,Tree2[i]);
    	return c;
    }
    int QueryMin(int i){
    	int c=INF; for(;i;i-=(i&(-i))) c=min(c,Tree1[i]);
    	return c;
    }
    void Init(){
    	for(int i=1;i<=n;++i){
    		if((i>>Table[i-1])&1) Table[i]=Table[i-1];
    		else Table[i]=Table[i-1]+1;
    		B[i].val=A[i],B[i].id=i;
    		Tree1[i]=Tree2[i]=INF;
    	} 
    	sort(B+1,B+1+n);
    	cnt=1;	A[B[1].id]=cnt;
    	for(int i=2;i<=n;A[B[i].id]=cnt,++i)
    		if(B[i].val!=B[i-1].val) ++cnt;
    	for(int i=n;i>=1;--i){
    		maxn[i]=QueryMax(A[i]+1);
    		minn[i]=QueryMin(A[i]-1);
    		Insert(A[i],i);
    		Maxn[i][0]=i;
    		Minn[i][0]=i;
    		for(int j=1;j<19;++j){
    			if(i+(1<<(j-1))<=n){
    				if(A[Maxn[i][j-1]]>A[Maxn[i+(1<<(j-1))][j-1]]) Maxn[i][j]=Maxn[i][j-1];
    				else Maxn[i][j]=Maxn[i+(1<<(j-1))][j-1];
    				if(A[Minn[i][j-1]]<A[Minn[i+(1<<(j-1))][j-1]]) Minn[i][j]=Minn[i][j-1];
    				else Minn[i][j]=Minn[i+(1<<(j-1))][j-1];
    			}
    			else{
    				Maxn[i][j]=Maxn[i][j-1];
    				Minn[i][j]=Minn[i][j-1];
    			}
    		}
    	}
    }
    inline int queryMax(int l,int r){
    	int len=Table[r-l+1];
    	if(A[Maxn[l][len]]<A[Maxn[r-(1<<len)+1][len]]) return Maxn[r-(1<<len)+1][len];
    	else return Maxn[l][len];
    }
    inline int queryMin(int l,int r){
    	int len=Table[r-l+1];
    	if(A[Minn[l][len]]>A[Minn[r-(1<<len)+1][len]]) return Minn[r-(1<<len)+1][len];
    	else return Minn[l][len];
    }
    int main(){
    	Read(n);
    	for(int i=1;i<=n;++i) Read(A[i]),D[i]=A[i];
    	Init();
    	for(int k=1;k<=n;++k){
    		int l=1,r=k,a,b,c,d;
    		LL ans=0;
    		while(r<=n){
    			l=r-k+1;
    			a=queryMax(l,r);
    			b=queryMin(l,r);
    			ans=max(ans,(LL)D[a]*D[b]);
    			c=maxn[a],d=minn[b];
    			a+=k,b+=k;
    			r=min(min(c,d),min(a,b));
    		}
    		printf("%lld\n",ans);
    	}
    	return 0;
    }
    

    题解给的做法是这样的。
    一个数向右一直选取比他大的数并更新其本身,这样的操作次数是O(logn)的。
    证明详请看题解
    有人说题解的证明是伪证,他的证明在这里:讨论
    更详细的讨论在这里:讨论帖
    定义F[i]表示长度为i的区间最大值,那么有结论F[i]>=F[i+1]。这是显然的,可以从子结构的角度考虑。
    定义pre[i],suc[i]表示向前向后第一个比i位置的数小的数的位置。
    我们枚举区间最大值,然后向左向右同时跳pre,suc去找最小值是谁,这样的复杂度是\(O(logn\times logn)\)的。
    所以我们枚举一个i表示当最大值可能为i。然后从i开始分别向左向右跳pre,suc,更新可能以i为最大值,min(A[l],A[r])为最小值的最大区间长度,这样的复杂度为\(O(nlog^2n)\)
    最后,在更新一遍F[i]=max(F[i],F[i+1])。
    最终正确性有保证,但是过程中的F[i]不一定就是最优答案,明白这个就好理解了。
    code

    #include<stdio.h>
    #include<algorithm>
    using namespace std;
    typedef long long LL;
    void Read(int& x){
        char ch; while(ch=getchar(),ch<'0'||ch>'9');
        x=ch-'0'; while(ch=getchar(),ch>='0'&&ch<='9') x=x*10+ch-'0';
    }
    int n,A[100010];
    struct Node{
        int val,id;
        Node(int a=0,int b=0){val=a,id=b;}
    }sta[100010];
    int siz=0,Pre[100010],Suc[100010];
    LL F[100010];
    int main(){
        Read(n);
        for(int i=1;i<=n;++i) Read(A[i]);
        for(int i=1;i<=n;++i){
            while(siz&&sta[siz].val>A[i]) --siz;
            if(siz) Pre[i]=sta[siz].id;
            else Pre[i]=0;
            sta[++siz]=Node(A[i],i);
        }
        siz=0;
        for(int i=n;i>=1;--i){
            while(siz&&sta[siz].val>A[i]) --siz;
            if(siz) Suc[i]=sta[siz].id;
            else Suc[i]=n+1;
            sta[++siz]=Node(A[i],i);
        }
        for(int i=1;i<=n;++i){
            int l=i,r=i;
            F[r-l+1]=max(F[r-l+1],(LL)A[i]*min(A[l],A[r]));
            for(;l;l=Pre[l]){
                for(int x=r;x<=n;x=Suc[x]){
                    F[Suc[x]-1-Pre[l]]=max(F[Suc[x]-1-Pre[l]],(LL)A[i]*min(A[x],A[l]));
                }
            }
        }
        for(int i=n;i>=1;--i) F[i]=max(F[i],F[i+1]);
        for(int i=1;i<=n;++i) printf("%lld\n",F[i]);
        getchar(); getchar();
        return 0;
    }
    

    在讨论帖中有人提出了严格O(n)的做法,而且不依赖于随机数据,有兴趣的可以去参考一下。

  • 相关阅读:
    【leetcode】1630. Arithmetic Subarrays
    【leetcode】1629. Slowest Key
    【leetcode】1624. Largest Substring Between Two Equal Characters
    【leetcode】1620. Coordinate With Maximum Network Quality
    【leetcode】1619. Mean of Array After Removing Some Elements
    【leetcode】1609. Even Odd Tree
    【leetcode】1608. Special Array With X Elements Greater Than or Equal X
    【leetcode】1603. Design Parking System
    【leetcode】1598. Crawler Log Folder
    Java基础加强总结(三)——代理(Proxy)Java实现Ip代理池
  • 原文地址:https://www.cnblogs.com/kito/p/7002926.html
Copyright © 2011-2022 走看看