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; }