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] \} ,i\leqslant k\leqslant 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],i\leqslant k\leqslant g\) 和 \(f[k+1][j]+a[k], g < k\leqslant 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]," \n"[j==n-i+1]);
	
	cout<<f[1][n]<<endl;
	return 0;
}

 

我們重新來看一下維護的東西.

\(f[i][k-1]+a[k],i\leqslant k\leqslant g[i][j]\) \(f[k+1][j]+a[k], g[i][j] < k\leqslant 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]," \n"[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]," \n"[j==n-i+1]);
		
	cout<<f[1][n]<<endl;
	return 0;
}

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM