【序列分段】【区间dp】[UVA12991] Game Rooms

【序列分段】【区间dp】[UVA12991] Game Rooms

一个 \(N\) 层的大楼,每层只有一个游戏室,可以设置一个乒乓球桌或游泳池。第 \(i\) 层有 \(T_i\) 个人喜欢乒乓球和 \(P_i\) 个人喜欢游泳。

现在要求使每个人到最近的喜欢的类型的活动室的距离的最小,且这栋大楼要有至少一个乒乓球桌和至少一个游泳池。

思路

考虑到要不乒乓球桌,要不游泳池。所以只有\(0/1\)两种情况(很明显不建白不建)

那么我们一整座大楼,从下往上(从第一层开始)就可以表示成一段\(01\)串。这些\(01\)串有连续的,有单独的。

那么我们也可以把单独的看成长度是\(1\)的连续串。

于是乎我们\(dp_{i,tpe\in\{0,1\}}\)表示强制\(i\)\(tpe\)。那么以\(i\)结尾一定会有一个连续都是\(tpe\)的串。我们找到这个\(tpe\)的极大连续后缀,假设为\([k,i]\),那么很明显首先\(k\neq1\),否则另一种就没地方建了。

然后因为\([k,i]\)是极大联通子串,因此\(k-1\)\(i+1\)一定是\(tpe\oplus 1\),那么我们就可以从\(dp_{k-1,tpe\oplus1}\)转移过来。

同时\([k,i]\)全部为\(tpe\)的代价我们也是可以知道的,假设为\(cst(k,i,tpe)\)

由于我们的状态转移已经用去\(O(n^2)\)了,因此我们的\(cst\)只能在\(O(1)\)内求出。

因为\([k,i]\)全部是\(tpe\),所以\(pep_{tpe,[k,i]}\)(people)是不需要考虑的了。同时因为懒惰所以\(mid=(k+i)/2\)以上的\(pep_{tpe\oplus1}\)都去\(i+1\),以下的都去\(k-1\),因此我们也可以得出\(cst\)的表达式

\[cst(k,i,tpe)= \sum_{j=k}^i pep_{tpe\oplus 1,j}\times \begin{cases} (j-k+1)&j\leq mid\\ (i+1-j)&j>mid \end{cases} \]

先考虑\(j\leq mid\)这一段,他像是一个阶梯式累加的结果\(1a_1+2a_2+3a_3+\dots\)不同的就是他不是从\(1\)开始的,他是\(1a_k+2a_{k+1}+3a_{k+2}+\dots\),那么我们可以定义前缀和\(b_i=\sum_{j=1}^ia_i,c_i=\sum_{j=1}^ii\cdot a_i\)那么就有

\[1a_k+2a_{k+1}+3a_{k+2}+\ldots+(i-k+1)a_{i}\\=1a_1+2a_2+\ldots -(1a_1+2a_2+\ldots +(k-1)a_{k-1})-(k-1)(a_k+a_{k+1}+a_{k+2}+\ldots)\\ =c_i-c_{k-1}-(k-1)(b_i-b_{k-1}) \]

然后代入\(i=mid\)就可以求出这半段的花费\(c_{mid}-c_{k-1}-(k-1)(b_{mid}-b_{k-1})\)

至于后半段,他是一个逆着的,所以我们可以定义\(d_i=\sum_{j=1}^ib_i=\sum_{j=1}^i\sum_{k=1}^ja_k=\sum_{j=1}^i(i-j+1)a_j\),这样这个阶梯就是逆着的了。具体推一下

\[(i-k+1)a_k+\ldots+2a_{i-1}+1a_i\\ =ia_1+(i-1)a_2+\ldots+1a_i-(ia_1+(i-1)a_2+\ldots+(i-k+2)a_{k-1})\\ =d_i-\bigg[(i-k+2)a_1+(k-2)a_1+(i-k+2)a_2+(k-3)a_2+\ldots+(i-k+2)a_{k-1}\bigg]\\ =d_i-(i-k+2)(a_1+a_2+\ldots+a_{k-1})-\bigg[(k-2)a_1+(k-3)a_2+\ldots+1a_{k-2}\bigg]\\ =d_i-(i-k+2)b_{k-1}-d_{k-2} \]

然后代入\(k=mid+1\),可以得到后半段为\(d_i-(i-mid+1)b_{mid}-d_{mid-1}\)

总结一下,

\[dp_{i,tpe}=\min_{k=2}^{i-1}\{dp_{k-1,tpe\oplus1}+cst(k,i,tpe)\}\quad(i>2) \\ dp_{2,0}=pep_{0,1}+pep_{1,2},\quad dp_{2,1}=pep_{0,2}+pep_{1,1} \\ cst(k,i,tpe\oplus 1)=\bigg[c_{tpe,i}-c_{tpe,k-1}-(k-1)(b_{tpe,mid}-b_{tpe,k-1})\bigg]+\bigg[d_{tpe,i}-(i-k+2)b_{tpe,mid}-d_{tpe,mid-1}\bigg] \]

注意要开long long,以及特判\(i=n,k=1\)两种情况,这两种情况并不能向两边走,只能朝某一头走。

代码中把游戏室的类型放在第一维了。

View Code
#include <cstdio>
#include <algorithm>
#include <cstring>
const int N=4096;
using std::min;
long long int pep[2][N],b[2][N],c[2][N],d[2][N];
long long int dp[2][N];
int T,n;
inline void init()
{
    scanf("%d",&n);
    for(register int i=1;i<=n;++i) scanf("%lld%lld",&pep[0][i],&pep[1][i]);
    memset(dp,0x6f,sizeof(dp));
    for(register long long int i=1;i<=n;++i) b[0][i]=b[0][i-1]+pep[0][i], b[1][i]=b[1][i-1]+pep[1][i];
    for(register long long int i=1;i<=n;++i) c[0][i]=c[0][i-1]+pep[0][i]*i, c[1][i]=c[1][i-1]+pep[1][i]*i;
    for(register long long int i=1;i<=n;++i) d[0][i]=d[0][i-1]+b[0][i], d[1][i]=d[1][i-1]+b[1][i];
    dp[0][0]=dp[1][0]=0;
}
inline long long int cst(int k,int i,int tpe)
{
    tpe^=1;
    if(k==i) return pep[tpe][k];
    if(i==n) return c[tpe][i]-c[tpe][k-1]-(k-1)*(b[tpe][i]-b[tpe][k-1]);
    if(k==1) return d[tpe][i];
    static long long int res=0;
    int mid=(i+k)>>1;
    res=c[tpe][mid]-c[tpe][k-1]-(k-1)*(b[tpe][mid]-b[tpe][k-1]);
    res+=(d[tpe][i]-(i-mid+1)*b[tpe][mid]-d[tpe][mid-1]);
    return res;
}
inline void solve()
{
    static int cases=0;
    init();
    printf("Case #%d: ",++cases);
    for(register int i=1;i<=n;++i)
    {
        for(register int k=i==n?2:1;k<=i;++k)
        {
            dp[0][i]=min(dp[0][i],dp[1][k-1]+cst(k,i,0));
            dp[1][i]=min(dp[1][i],dp[0][k-1]+cst(k,i,1));
        }
    }
    printf("%lld\n",min(dp[0][n],dp[1][n]));
}
int main()
{
    scanf("%d",&T);
    while(T--) solve();
    return 0;
}
posted @ 2022-03-28 13:11  IdanSuce  阅读(66)  评论(0编辑  收藏  举报