输入格式
第一行给出数据组数 TTT( 1≤T≤1001\leq T\leq 1001≤T≤100)。接下来对于每组数据,首先一行给出 NNN( 2≤N≤40002\leq N\leq 40002≤N≤4000),代表该大楼一共有多少层;接下来 NNN 行第 iii 行给出两个整数 Ti,PiT_i, P_iTi,Pi( 1≤Ti,Pi≤1091\leq T_i, P_i\leq 10^91≤Ti,Pi≤109),代表第 iii 层的人数,意义同题意
输出格式
对于第 xxx 组( xxx 从 111 开始标号)数据的答案 yyy,在第 xxx 行输出 Case #x: y
样例解释
在第一层设置乒乓球桌,在第二层设置游泳池。这样 555 个人要从第一层走到第二层, 444 个人要从第二层走到第一层,距离和为 999
Translated by @Piwry
输入输出样例
输入 #1
1 2 10 5 4 3
输出 #1
Case #1: 9
解析
我们可以将大楼的设置情况看为:从底层到顶层,连续的一段楼层(长度可以为 111)设为乒乓球桌,再连续的一段设为游泳池... 这样循环
对于设置相同的连续的一段楼层中的人(且这是个极大连续段。例如这一段是 [l,r][l, r][l,r] 且设置的都是乒乓球桌,那么位置 l−1l-1l−1 以及 r+1r+1r+1 都必须设置游泳池),如果这一段设置的是乒乓球桌,显然我们就不需要考虑去打乒乓球的人的贡献,因为他们自己所在的楼层就有其想要的设施;设这一段为 [l,r][l, r][l,r],而对于去游泳的人,显然最近的游泳池就在位置 l−1l-1l−1 或 r+1r+1r+1
于是我们设状态 dp(i,0/1)dp(i, 0/1)dp(i,0/1),表示第 iii 层是乒乓桌(000)或游泳池(111),前 iii 层的最小距离和。
转移时,我们只需枚举以 iii 为右端点的极大连续段。则转移方程就为 dp(i,0/1)=dp(j,1/0)+cost(j+1,i,1/0)dp(i, 0/1)=dp(j, 1/0)+cost(j+1, i, 1/0)dp(i,0/1)=dp(j,1/0)+cost(j+1,i,1/0),其中 0≤j<i0\leq j<i0≤j<i,cost(l,r,0/1)cost(l, r, 0/1)cost(l,r,0/1) 代表 [l,r][l, r][l,r] 区间内要去最近的(l−1l-1l−1 或 r+1r+1r+1)乒乓桌(000)或游泳池(111)的距离和
关于计算 costcostcost 函数,首先有一个分界点 (l+r)>>1
,在分界点右边的人显然往 r+1r+1r+1 走最近;左边的人同;分界点走左或右都可以。接着这个东西用二阶前缀后缀算一下就行了(O(1)O(1)O(1))
(感觉 costcostcost 函数的计算方式大家都会,不过这里还是讲下X)
我们先来看看我们算的大概是什么东西。考虑 [l,r][l, r][l,r] 区间内的人都往 l−1l-1l−1 走,他们走的距离和:
(a[i]
是该层对应的人数)
差不多就是这样一个东西;
而二阶前缀和算的也是这样一个东西
不过我们计算时可能要从二阶前缀和中取一段,就像这样:
(其中 suf
就是后缀的意思,ssuf
就是二阶后缀(后后缀X))
如图中注译所述的,我们可以把它拆成 ssuf[l]-(l+r-1)*suf[r+1]-ssuf[r+1]
,于是就可以 O(1)O(1)O(1) 计算了
(讲得还是挺简略的,其实画画图就可以理解了)
代码
#include <cstdio>
#include <iostream>
#define ll long long
using std::min;
const int MAXN =4050;
inline int read(){
int x =0; char c =getchar();
while(c < '0' || c > '9') c =getchar();
while(c >= '0' && c <= '9') x = (x<<3) + (x<<1) + (48^c), c =getchar();
return x;
}
int n;
ll dp[MAXN][2];/*dp 至 i 层,该 i 层是乒乓 0/游泳 1*/
ll pre[MAXN][2], ppre[MAXN][2], suf[MAXN][2], ssuf[MAXN][2];
int a[MAXN][2];
/*新产生的贡献*/
/*[l, r] 都放 typ*/
/*并且要求该区间是一个同色极大区间*/
inline ll cost(int l, int r, bool typ){
if(l == 1 && r == n)
return 0x3f3f3f3f3f3f3f3f;
else if(l == 1)
return ppre[r][!typ];
else if(r == n)
return ssuf[l][!typ];
else{
ll ret =0;
int mid =(l+r)>>1;/*mid 位置的人向上向下贡献都一样*/
/*下面两行可以画图想想前前缀和和后后缀和的样子 ( 阶梯和 )*/
ret +=ssuf[l][!typ]-suf[mid+1][!typ]*(mid-l+1)-ssuf[mid+1][!typ];
ret +=ppre[r][!typ]-pre[mid][!typ]*(r-(mid+1)+1)-ppre[mid][!typ];
return ret;
}
}
int main(){
for(int t =0, T =read(); t < T; ++t){
n =read();
for(int i =1; i <= n; ++i)
a[i][0] =read(), a[i][1] =read();
for(int i =1; i <= n; ++i){
pre[i][0] =pre[i-1][0]+a[i][0];
pre[i][1] =pre[i-1][1]+a[i][1];
}
for(int i =1; i <= n; ++i){
ppre[i][0] =ppre[i-1][0]+pre[i][0];
ppre[i][1] =ppre[i-1][1]+pre[i][1];
}
suf[n+1][0] =ssuf[n+1][0] =suf[n+1][1] =ssuf[n+1][1] =0;
for(int i =n; i >= 1; --i){
suf[i][0] =suf[i+1][0]+a[i][0];
suf[i][1] =suf[i+1][1]+a[i][1];
}
for(int i =n; i >= 1; --i){
ssuf[i][0] =ssuf[i+1][0]+suf[i][0];
ssuf[i][1] =ssuf[i+1][1]+suf[i][1];
}
for(int i =1; i <= n; ++i){
dp[i][0] =dp[i][1] =0x3f3f3f3f3f3f3f3f;
for(int j =0; j < i; ++j){
dp[i][0] =min(dp[i][0], dp[j][1]+cost(j+1, i, 0));
dp[i][1] =min(dp[i][1], dp[j][0]+cost(j+1, i, 1));
}
}
printf("Case #%d: %lld\n", t+1, min(dp[n][0], dp[n][1]));
}
}