题解 P6563 【[SBCOI2020] 一直在你身旁】

devout

2020-11-21 14:27:37

Solution

显然有一个 $O(n^3)$ 的暴力dp,设 $f_{i,j}$ 表示确定一个值在区间 $[l,r]$ 上的最小花费,那么显然有转移 $$f_{i,j}=\min\{\max(f_{i,k},f_{k+1,j})+a_k\}$$ 根据区间dp的做法就可以解决了。 考虑优化,我们可以注意到,对于 $i$ 固定时,$f_{i,j}$ 是单调不减的,$j$ 固定时,$f_{i,j}$ 是单调不增的,所以对于一个区间 $[i,j]$,我们一定可以找到一个位置 $x$,是的对于 $\forall k\in[i,x],f_{i,k}<f_{k+1,j},\forall k\in(x,j],f_{i,k}>f_{k+1,j}$ 并且我们观察到,当 $j$ 固定的时候,显然决策点 $x$ 随着 $i$ 的减小而单调不增。 我们考虑先枚举 $j$,然后倒序枚举 $i$ 所以可以考虑把转移分成两部分,对于在 $x$ 左边的部分,即用 $\min\{f_{k+1,j}+a_k\}$ 的部分转移的,我们可以用单调队列维护,对于在 $x$ 右边的部分,$f_{i,k}$单调不减,$a_k$ 单调不减,所以最优决策点一定在 $x+1$ 的位置上 这样一来,总复杂度就可以做到 $O(n^2)$ 了。 代码比较短,但是写起来真的是费劲: ```cpp #include <bits/stdc++.h> using namespace std; # define Rep(i,a,b) for(int i=a;i<=b;i++) # define _Rep(i,a,b) for(int i=a;i>=b;i--) # define RepG(i,u) for(int i=head[u];~i;i=e[i].next) typedef long long ll; const int N=7100; template<typename T> void read(T &x){ x=0;int f=1; char c=getchar(); for(;!isdigit(c);c=getchar())if(c=='-')f=-1; for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+c-'0'; x*=f; } int t,n; int a[N]; ll f[N][N]; int q[N],head,tail; int main() { memset(f,0x3f,sizeof(f)); read(t); while(t--){ read(n); Rep(i,1,n)read(a[i]); Rep(i,1,n)f[i][i]=0; for(int j=1;j<=n;j++){ head=1,tail=0; int k=j-1; for(int i=j-1;i;i--){ while(k>=i&&f[i][k]>f[k+1][j])k--; while(head<=tail&&q[head]>k)head++; while(head<=tail&&f[q[tail]+1][j]+a[q[tail]]>f[i+1][j]+a[i])tail--; q[++tail]=i; f[i][j]=min(f[i][j],f[i][k+1]+a[k+1]); f[i][j]=min(f[i][j],f[q[head]+1][j]+a[q[head]]); } } printf("%lld\n",f[1][n]); Rep(i,1,n)Rep(j,1,n)f[i][j]=1e18; } return 0; } ```