题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5550
题目:一共n层楼,每层楼都要建一个娱乐室,共有两种(球馆、游泳馆)可以选择,且n层楼中至少有1个球馆和游泳馆。现每层楼有Ti个球类爱好者,Pi个游泳爱好者,他们都要去各自的娱乐室娱乐,求一个建造方案,使得他们走的总路程的最小。(假设A在3楼,要去游泳。若游泳馆在3楼,则A走的路程为0;若游泳馆在2楼或4楼,则路程为1,;若在1楼或5楼,则路程为2.以此类推)。
思路:令dp(i, j)表示已经安排了前i层的人的最小花费,j为0或1,表示娱乐室的类别。我们让某一段连续楼层(从k开始)一直到i全为娱乐室j。易知,第k-1层和第i+1层均为j^1。
那么dp(i, j) = min{ dp(k, j^1) + dist(k+1, i) }, 其中dist(k+1, i)表示安排第k+1层到第i层人的最小花费,因为k+1到i层的娱乐室都一样,所以可以在常数时间内算出。总复杂度为O(n²)。
dist(k+1, j)的算法如下
#include<cstdio>
#include<cstring>
#include<vector>
#include<iostream>
#include<set>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 4000 + 5;
const ll INF = 1e15 + 10;
int T[maxn], P[maxn], n;
ll sumT[maxn], sumP[maxn], calT[maxn], calP[maxn];
ll dp[maxn][2];
ll toLeft(int L, int R, ll *sum, ll *cal) {
return (cal[R] - cal[L-1]) - (L-1) * (sum[R] - sum[L-1]);
}
ll toRight(int L, int R, ll *sum, ll *cal) {
return (R+1)*(sum[R]-sum[L-1]) - (cal[R] - cal[L-1]);
}
ll dist(int L, int R, int p) { // p为0代表L——R区间中全是球馆,p为1代表L——R区间中全是游泳馆
if(L == 1 && R == n) return INF; //n层楼全是一种是不合法的
else if(L == 1) { //只能去右边(即楼上)
if(p == 1)
return toRight(L, R, sumT, calT);
return toRight(L, R, sumP, calP);
}
else if(R == n) { //只能去左边(即楼下)
if(p == 1)
return toLeft(L, R, sumT, calT);
return toLeft(L, R, sumP, calP);
}
else { //去楼上楼下皆可,因此区间左半部分去楼下,右半部分去楼上
int x = (L + R) >> 1;
if(p == 1)
return toLeft(L, x, sumT, calT) + toRight(x+1, R, sumT, calT);
return toLeft(L, x, sumP, calP) + toRight(x+1, R, sumP, calP);
}
}
int main() {
int test, kase = 0;
scanf("%d", &test);
while(test--) {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d%d", &T[i], &P[i]);
sumT[i] = sumT[i-1] + T[i];
sumP[i] = sumP[i-1] + P[i];
calT[i] = calT[i-1] + (ll)i*T[i];
calP[i] = calP[i-1] + (ll)i*P[i];
}
dp[0][0] = dp[0][1] = 0;
for(int i = 1; i <= n; i++)
for(int j = 0; j <= 1; j++) {
dp[i][j] = INF;
for(int k = 0; k < i; k++)
dp[i][j] = min(dp[i][j], dp[k][j^1] + dist(k+1, i, j));
}
printf("Case #%d: %lld\n", ++kase, min(dp[n][0], dp[n][1]));
}
return 0;
}