zoukankan      html  css  js  c++  java
  • BZOJ 2448: 挖油

    Description

    [0,x]中全是1,其余全是0,每个点有一个权值,求最坏情况下得到x的最小权值.

    Sol

    DP+单调队列.

    首先就是一个 (O(n^3)) 的DP.

    (f[i][j]) 表示x在 (i,j) 之间的最小权值.

    转移就是 (f[i][j]=min { max { f[i][k-1],f[k+1][j] } +a[k] } ,ileqslant kleqslant j) 。

    一个记搜就是 (O(n^3)) 的.

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    using namespace std;
    #define N 2005
    int n;int a[N],f[N][N];
    inline int in(int x=0,char ch=getchar(),int v=1){
        while(ch!='-'&&(ch>'9'||ch<'0')) ch=getchar();if(ch=='-') v=-1,ch=getchar();
        while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*v; }
    int DFS(int l,int r){
        if(l>r) return 0;if(l==r) return f[l][r]=a[l];
        int &ans=f[l][r];if(~ans) return ans;ans=0x7fffffff;
        for(int i=l;i<=r;i++) ans=min(ans,max(DFS(l,i-1),DFS(i+1,r))+a[i]);
        return ans;
    }
    int main(){
        n=in();for(int i=1;i<=n;i++) a[i]=in();
        memset(f,-1,sizeof(f));
        cout<<DFS(1,n);return 0;
    }
    

    然后考虑优化,我们发现其实可以把 (max) 去掉.

    因为 (f[i][j]) 固定任意一段,随区间长度增长是单调递增的.

    那么关于分割点 (g) 我们就可以二分了.

    然后就是可以维护 (f[i][k-1]+a[k],ileqslant kleqslant g) 和 (f[k+1][j]+a[k], g < kleqslant j) .

    这个可以通过建以 (i) 和 (j) 为端点的线段树向左向右来维护.

    复杂度 (O(n^2logn)) .差不多可以通过本题了.

    但是我们发现还可以继续优化,因为 (g[i][j-1] leqslant g[i][j],g[i][j] leqslant g[i+1][j]) .

    这个过程是 (O(n)) 的.

    然后维护最小值就可以用单调队列.

    一开始我非常的naive,只用了2个队列来维护,然后写个程序来对拍直接gg.

    #include<cstdio>
    #include<iostream>
    using namespace std;
    
    const int N = 2005;
    
    int n,t[N];
    int f[N][N];
    int q1[N],h1,t1;// [i,g]
    int q2[N],h2,t2;// (g,j]
    
    inline int in(int x=0,char ch=getchar(),int v=1){
    	while(ch!='-' && (ch>'9'||ch<'0')) ch=getchar();if(ch=='-') v=-1,ch=getchar();
    	while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*v; }
    
    int main(){
    //	freopen("in.in","r",stdin);
    	n=in();
    	for(int i=1;i<=n;i++) t[i]=in();
    	
    	for(int i=n;i;i--){
    		f[i][i]=t[i],f[i][i-1]=0;
    		h1=h2=1,t1=t2=0;
    		q1[++t1]=i;
    		
    		int g=i;
    //		int tmpm=i;
    		for(int j=i+1;j<=n;j++){
    			//q1 - 加入t[j]
    			while(h1<=t1 && f[i][q1[t1]-1]+t[q1[t1]] > f[i][j-1]+t[j]) t1--;
    			q1[++t1]=j;
    			
    			//分割点
    			for(;g<j && f[i][g-1] < f[g+1][j];g++){
    				//q1 del g
    				if(q1[h1] == g) h1++; 
    				//q2 add g
    				while(h2<=t2 && f[q2[t2]+1][j]+t[q2[t2]] > f[g+1][j]+t[g]) t2--;
    				q2[++t2]=g;
    //				if(f[tmpm][j]+t[tmpm] > f[g+1][j]+t[g]) tmpm=g;
    			}
    			
    			//计算f[i][j] 
    			f[i][j]=min(f[i][q1[h1]-1]+t[q1[h1]],f[q2[h2]+1][j]+t[q2[h2]]);
    //			f[i][j]=min(f[i][j],f[tmpm][j]+t[tmpm]);
    		}
    //		for(;h2<=t2;h2++) f[i][n]=min(f[i][n],f[q2[h2]+1][n]+t[q2[h2]]);
    	}
    	
    //	for(int i=1;i<=n;i++) for(int j=1;j<=n-i+1;j++) printf("%d%c",f[j][j+i-1]," 
    "[j==n-i+1]);
    	
    	cout<<f[1][n]<<endl;
    	return 0;
    }
    

    我们重新来看一下维护的东西.

    (f[i][k-1]+a[k],ileqslant kleqslant g[i][j]) (f[k+1][j]+a[k], g[i][j] < kleqslant j) .

    可以发现一个 (i) 是固定的,第二个 (j) 是固定的,我们可以用这个性质来维护.

    就是用 (n+1) 个单调队列来维护,用一个单调队列维护 (i) 随 (j) 增长时的最小值.

    其他的维护右端点 (j) 固定时,随 (i) 递减的最小值.

    注意一下入队和出队就可以了.

    对于 (i) 固定时,需要出队的是 ((g[i-1][j],g[i][j])) ,入队的是 (j) .

    对于 (j) 固定时,需要出队的是 ((g[i+1][j],g[i][j])) ,入队的是 (i) .

    还有一点就是 (f[i][j]) 用到 (f[i][k-1],f[k+1][j]) ,所以 (i) 需要倒着枚举.

    这个样子 复杂度就变成了 (O(n^2)) 啦!

    PS:双倍经验 BZOJ 2412

    Code

    /**************************************************************
        Problem: 2448
        User: BeiYu
        Language: C++
        Result: Accepted
        Time:1316 ms
        Memory:48420 kb
    ****************************************************************/
    #include<cstdio>
    #include<iostream>
    using namespace std;
    
    const int N = 2005;
    #define A(x) (f[i][x-1]+a[x])
    #define B(x) (f[x+1][j]+a[x])
    
    int n,a[N];
    int f[N][N],g[N][N];
    int q[N][N],h[N],t[N];
    
    inline int in(int x=0,char ch=getchar(),int v=1){
    	while(ch!='-' && (ch>'9'||ch<'0')) ch=getchar();if(ch=='-') v=-1,ch=getchar();
    	while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*v; }
    
    int main(){
    //	freopen("in.in","r",stdin);
    	n=in();
    	for(int i=1;i<=n;i++) a[i]=in();
    	
    	for(int i=n;i;--i){
    		f[i][i]=a[i],g[i][i]=i;
    		
    		//f[i][j]=min{ f[i][k-1]+t[k] },g[i][j]<=k<=j;  =>q[0]
    		//f[i][j]=min{ f[k+1][j]+t[k] },i<=k<g[i][j];  =>q[j]
    		
    		h[0]=1,t[0]=0;
    		h[i]=1,t[i]=0;
    		q[i][++t[i]]=i;
    		
    		for(int j=i+1;j<=n;++j){
    			//g[i][j]
    			g[i][j]=g[i][j-1];
    			while(g[i][j]<j && f[i][g[i][j]-1] < f[g[i][j]+1][j]) ++g[i][j];
    			
    			//q[0].pop g[i][j-1]--(g[i][j]-1) 
    			for(int k=g[i][j-1];k<g[i][j];++k)
    				if(q[0][h[0]] == k) ++h[0];
    			//j->q[0]
    			while(h[0]<=t[0] && A(q[0][t[0]]) > A(j)) --t[0];
    			q[0][++t[0]]=j;
    			
    			//q[j].pop g[i+1][j]-g[i][j]
    			for(int k=g[i+1][j];k>=g[i][j];--k)
    				if(q[j][h[j]] == k) ++h[j];
    			//i->q[j]
    			while(h[j]<=t[j] && B(q[j][t[j]]) > B(i)) --t[j];
    			q[j][++t[j]]=i;
    			
    			//f[i][j]
    			f[i][j]=min(A(q[0][h[0]]),B(q[j][h[j]]));
    			
    		}
    	}
    	
    //	for(int i=1;i<=n;i++) for(int j=1;j<=n-i+1;j++) printf("%d%c",g[j][j+i-1]," 
    "[j==n-i+1]);
    //	cout<<"***"<<endl;
    //	for(int i=1;i<=n;i++) for(int j=1;j<=n-i+1;j++) printf("%d%c",f[j][j+i-1]," 
    "[j==n-i+1]);
    		
    	cout<<f[1][n]<<endl;
    	return 0;
    }
    

      

  • 相关阅读:
    Cookie练习
    JS写九九乘法表
    对GridView实现分页
    对GridView的行加颜色并弹出Kindeditor
    对Dictionary的理解
    一、android 开发环境大搭建
    main方法的测试
    main 方法的书写(1)
    由InvocationTargetException引发的思考
    汇编学习笔记之处理器体系结构
  • 原文地址:https://www.cnblogs.com/beiyuoi/p/5974574.html
Copyright © 2011-2022 走看看