USACO16FEB 再探圆形谷仓(斜率优化DP)

版权声明:我这种蒟蒻的文章,真正的大佬一般看不上的_(:з」∠)_ https://blog.csdn.net/Paulliant/article/details/83217139

题意

n n 个顺时针排列的牛棚,可以开 k k 个口让牛顺时针进入,已知每个牛棚需要多少只牛,问所有牛在牛棚内行走的距离总和最小值。
1 n 100 1 \leq n \leq 100
1 k 7 1 \leq k \leq 7

思路

序列倍长,断环成链,区间转移,线性动归,这种套路实在见得多了,不难打出一个 n 3 n^3 d p dp 如下:

FOR(r,0,n-1)
{
	FOR(i,1,n)cnt[i]=cnt[i-1]+a[r+i],sum[i]=sum[i-1]+a[r+i]*i;
	memset(dp,0x3f,sizeof(dp));
	dp[0][0]=0;
	FOR(i,1,K)
		FOR(j,1,n)
			FOR(k,0,j-1)
				dp[i][j]=min(dp[i][j],dp[i-1][k]+(sum[j]-sum[k])-(k+1)*(cnt[j]-cnt[k]));
	ans=min(ans,dp[K][n]);
}

现在优化掉其中一维即可,显然是转移的那一维,列出转移式:
d p i , j = min { d p i 1 , k + ( s u m j s u m k ) + ( k + 1 ) ( c n t k c n t j ) } , k [ 0 , j 1 ] dp_{i,j}=\min\{dp_{i-1,k}+(sum_j-sum_k)+(k+1)(cnt_k-cnt_j)\},k\in[0,j-1]
把和 j j 有关或常数提出来,将只和 k k 有关、与 j , k j,k 有关的分别并列得到:
d p i , j = min { d p i 1 , k s u m k + c n t k ( k + 1 ) c n t j ( k + 1 ) } + s u m j , k [ 0 , j 1 ] dp_{i,j}=\min\{dp_{i-1,k}-sum_k+cnt_k(k+1)-cnt_j(k+1)\}+sum_j,k\in[0,j-1]
X i = i + 1 , Y i , j = d p i , j s u m j + c n t j ( j + 1 ) , K i = c n t i , C i = s u m i X_i=i+1,Y_{i,j}=dp_{i,j}-sum_j+cnt_j(j+1),K_i=cnt_i,C_i=sum_i
原式又变成 d p i , j = min { Y i 1 , k K j X k } + C j dp_{i,j}=\min\{Y_{i-1,k}-K_jX_k\}+C_j
维护一个 ( X k , Y i 1 , k ) (X_k,Y_{i-1,k}) 的下凸包,即保证斜率一定时, y y 轴上截距最小,而斜率的 K i K_i 又是单调递增的,那斜率优化就显然了。
总结一下斜率是如何优化 D P DP 的。
回到我们的单调队列优化 D P DP d p i = min { F j } + G i dp_i=\min\{F_j\}+G_i max \max 相同。
其中 F j F_j 是仅与 j j 相关的函数, G i G_i 是只与 i i 相关的函数,维护一个关于 F j F_j 的单调队列即可。
而当出现了 d p i = min { Y j K i X j } + C i dp_i=\min\{Y_j-K_iX_j\}+C_i 这种 min \min 函数内的式子与转出者 j j 和转入者 i i 均有关的式子时,普通的单调队列已经无能为力。
我们联想到一次函数的解析式 y = k x + b y=kx+b ,把 ( X j , Y j ) (X_j,Y_j) 洒在坐标轴上,不难发现 Y j K i X j Y_j-K_iX_j 就是点斜式方程 y Y j = k ( x X j ) y-Y_j=k(x-X_j) 的在 y y 轴上的截距。对于上面这样一条转移式,其实求的就是若干个坐标轴上的点,拿一条斜率为 K i K_i 的直线去截某一个点,能得到最小的截加上常数 C i C_i ,当然 K i K_i 是递增的(有时通过排序保证单调性),那只用维护一个下凸包,每次转移前删去非最优的左端点再转移,加入点要保证形成下凸包。
max \max 也是同理,只不过下凸包换成了上凸包,斜率递增改成斜率递减即可。

代码

#include<bits/stdc++.h>
#define FOR(i,x,y) for(int i=(x),i##END=(y);i<=i##END;++i)
#define DOR(i,x,y) for(int i=(x),i##END=(y);i>=i##END;--i)
typedef long long LL;
using namespace std;
const int N=503;
bool cmp(LL x,LL y){return x<y;}
template<bool cmp(LL,LL)>struct mulnoque
{
    int L,R;LL Qx[N],Qy[N];
    mulnoque(){clear();}
    void clear(){L=1,R=0;}
    void push(LL x,LL y){Qx[++R]=x,Qy[R]=y;}
    void pop(LL k){while(L<R&&!cmp(k*(Qx[L+1]-Qx[L]),Qy[L+1]-Qy[L]))L++;}
    void del(LL x,LL y){while(L<R&&!cmp((Qy[R]-Qy[R-1])*(x-Qx[R]),(y-Qy[R])*(Qx[R]-Qx[R-1])))R--;}
    LL X(){return Qx[L];}LL Y(){return Qy[L];}
};
mulnoque<cmp>Q;
LL dp[13][N],cnt[N],sum[N];
int _a[2*N],*a=_a;
int n,K;
LL getY(int x,int y){return dp[x-1][y]-sum[y]+(y+1)*cnt[y];}
LL getK(int x){return cnt[x];}
LL getX(int x){return x+1;}
LL getC(int x){return sum[x];}
 
int main()
{
    scanf("%d%d",&n,&K);
    FOR(i,1,n)scanf("%d",&_a[i]);
    FOR(i,n+1,2*n)_a[i]=_a[i-n];
    LL ans=1e15;
    FOR(r,0,n-1)
    {
        FOR(i,1,n)cnt[i]=cnt[i-1]+a[r+i],sum[i]=sum[i-1]+a[r+i]*i;
        memset(dp,0x3f,sizeof(dp));
        dp[0][0]=0;
        FOR(i,1,K)
        {
            Q.clear();
            FOR(j,1,n)
            {
                LL x=getX(j-1),y=getY(i,j-1),k=getK(j),c=getC(j);
                Q.del(x,y);
                Q.push(x,y);
                Q.pop(k);
                dp[i][j]=Q.Y()-k*Q.X()+c;
            }
        }
        ans=min(ans,dp[K][n]);
    }
    printf("%lld\n",ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/Paulliant/article/details/83217139
今日推荐