UVA12991 Game Rooms - 动态规划 - 二阶前缀和 -
题解:
其实本题和区间 dp 关系不大,内核就是一个普通dp
考虑泳池和乒乓球,每种设施在楼中一定是连续的一段(长度可以为1),可能有多段
对于每个极大的这样的段(即两种设施的分界点)进行dp
设dp[i][0/1]
表示考虑到第 i
层楼,最后一段是水池/球,对应的最优解
dp[i][0] = dp[j][1] + cost(j+1,i,1)
(另一种转移同理)
下面我们的问题就是计算cost(l,r,0/1)
即如果[l,r]
都用水池/球的话,这段对答案产生的贡献。
显然只需要算另一种没有在这段楼里面添加的器材(比如如果全为水池,我只需要算球的贡献)
又显然可知,令mid=(l+r)/2
,则[l..mid]
肯定是去l-1
,[mid+1..r]
肯定去r+1
,当然要先把l=1
或r=n
或l=1且r=n
判掉(注意l=1且r=n
无解,取inf)
手算一下发现我们实际要求的是一个“阶梯和”,即①[l..mid]:(mid-l+1)*p[l]+(mid-l)*p[l+1]+..+p[mid] 对于②[mid+1..r]:p[mid+1]+...+(r-mid)*p[r]
发现这两种其实是不同的,这里我们采用二阶前缀和
sum[i] = sum[i-1]+t[i]
,如果summ[i] = summ[i-1] + sum[i]
,我们得到的是summ[n] = n*t[n] + ... + 1*t[1]
的形式,我们用这个来计算②,方法就是summ[r] - summ[mid] - (r-(mid+1)+1) * sum[mid]
同理我们维护一个后缀和,和后缀和的后缀和,就可以计算①了
一个很坑的点是多测,对前缀和不需要初始化,但对后缀和需要,因为每次n可能不同,suf[n+1]可能不为0
代码:
// by Balloons
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#define debug() cerr<<"Madoka"<<endl
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
using namespace std;
typedef long long LL;
#define int LL
const int inf = 1e18,maxn=4005;
int n,t[maxn],p[maxn]; // t 球 p 水
int preft[maxn][2],prefp[maxn][2]; // t[i]的前缀;前缀的前缀(1*a[1]+2*a[2]+..) p[i]的前缀 前缀的前缀
int suft[maxn][2],sufp[maxn][2]; // 后缀 后缀的后缀
int dp[maxn][maxn]; // dp[i][0/1] 到第 i 楼,最后一段是 水/球
int cst(int l,int r,int type){ // type==0 -> p[i] type==1 -> t[i]
if(l==r)return type==0?p[l]:t[l];
int mid = l+r>>1;
if(l == 1 && r == n)return 1e18;
if(r == n){
if(type == 0)return sufp[l][1];
else return suft[l][1];
}
if(l == 1){
if(type == 0)return prefp[r][1];
else return preft[r][1];
}
// [l..mid] (mid-l+1)*p[l]+(mid-l)*p[l+1]+..+p[mid] [mid+1..r] (r-mid)*p[r]+...+p[mid+1]
// sufp[l][1] - sufp[mid+1][1] - (mid-l+1) * sufp[mid+1][0];
// prefp[r][1] - prefp[mid][1] - (r-mid) * prefp[mid][0];
if(type == 0){
int t1 = sufp[l][1] - sufp[mid+1][1] - (mid-l+1) * sufp[mid+1][0];
int t2 = prefp[r][1] - prefp[mid][1] - (r-mid) * prefp[mid][0];
return t1 +t2;
}
if(type == 1){
int t1 = suft[l][1] - suft[mid+1][1] - (mid-l+1) * suft[mid+1][0];
int t2 = preft[r][1] - preft[mid][1] - (r-mid) * preft[mid][0];
return t1 +t2;
}
}
int tot=0;
void solve(){
scanf("%lld",&n);
for(int i=1;i<=n;i++)scanf("%lld%lld",&t[i],&p[i]);
for(int i=1;i<=n;i++)preft[i][0] = preft[i-1][0] + t[i],prefp[i][0] = prefp[i-1][0] + p[i];
for(int i=1;i<=n;i++)preft[i][1] = preft[i-1][1] + preft[i][0], prefp[i][1] = prefp[i-1][1] + prefp[i][0];
suft[n+1][0] = suft[n+1][1] = sufp[n+1][0] = sufp[n+1][1] = 0; //!!!!!
for(int i=n;i>=1;i--)suft[i][0] = suft[i+1][0] + t[i],sufp[i][0] = sufp[i+1][0] + p[i];
for(int i=n;i>=1;i--)suft[i][1] = suft[i+1][1] + suft[i][0],sufp[i][1] = sufp[i+1][1] + sufp[i][0];
for(int i=1;i<=n;i++)dp[i][0] = dp[i][1] = inf;
dp[0][0] = dp[0][1] = 0;
for(int i=1;i<=n;i++){
for(int j=0;j<=i-1;j++){
dp[i][0] = min(dp[i][0],dp[j][1]+cst(j+1,i,1));
dp[i][1] = min(dp[i][1],dp[j][0]+cst(j+1,i,0));
}
}
int ans = min(dp[n][1], dp[n][0]);
printf("Case #%lld: %lld\n",++tot,ans);
}
signed main(){
// freopen("UVA12991.in","r",stdin);
// freopen("UVA12991.out","w",stdout);
int te;scanf("%lld",&te);
while(te--)solve();
return 0;
}