DP HDU 5550

16 篇文章 0 订阅

Game Rooms

Time Limit: 4000/4000 MS (Java/Others)    Memory Limit: 65535/65535 K (Java/Others)
Total Submission(s): 814    Accepted Submission(s): 257


Problem Description
Your company has just constructed a new skyscraper, but you just noticed a terrible problem: there is only space to put one game room on each floor! The game rooms have not been furnished yet, so you can still decide which ones should be for table tennis and which ones should be for pool. There must be at least one game room of each type in the building.

Luckily, you know who will work where in this building (everyone has picked out offices). You know that there will be  Ti  table tennis players and  Pi  pool players on each floor. Our goal is to minimize the sum of distances for each employee to their nearest game room. The distance is the difference in floor numbers: 0 if an employee is on the same floor as a game room of their desired type, 1 if the nearest game room of the desired type is exactly one floor above or below the employee, and so on.
 

Input
The first line of the input gives the number of test cases,  T(1T100) T  test cases follow. Each test case begins with one line with an integer  N(2N4000) , the number of floors in the building.  N  lines follow, each consists of 2 integers,  Ti  and  Pi(1Ti,Pi109) , the number of table tennis and pool players on the  ith floor. The lines are given in increasing order of floor number, starting with floor 1 and going upward.
 

Output
For each test case, output one line containing  Case #x: y, where  x  is the test case number (starting from 1) and  y  is the minimal sum of distances.
 

Sample Input
  
  
1 2 10 5 4 3
 

Sample Output
  
  
Case #1: 9
Hint
In the first case, you can build a table tennis game room on the first floor and a pool game room on the second floor. In this case, the 5 pool players on the first floor will need to go one floor up, and the 4 table tennis players on the second floor will need to go one floor down. So the total distance is 9.
 

题意:有一栋大楼,大楼有 n 层,每一层 有 a 个乒乓球爱好者和 b个游泳爱好者,而这栋大楼的每一层都可以选择建设一个乒乓球场或是一个游泳池,如果当前层是游泳池,那么这一层的游泳爱好者前往游泳池所需花费为0,否则花费就是最近游泳池所在层与当前层的差值。现在要求你对它每一层进行选择建乒乓球场还是游泳池,使得所有人的花费之和最小

思路:对于每一层的人,去离他最近的地方就可以了,所以从下往上DP

DP[0][i] 表示第 i 层建乒乓球场 DP[1][i] 表示第 i 层建游泳馆

为了快速计算出各层的人的花费,作两个前缀和,一个是 sum[0][i] 表示从 第一层到 第 i 层的乒乓球爱好者人数

另一个是 dsum[0][i] 表示第一层到 第 i 层的乒乓球爱好者移动到第0层的总花费

那么要找[l + 1,r]的人移动到第r + 1 层的操作就简化为:

ll go_up(int s,int l,int r){
	return (sum[s][r] - sum[s][l]) * (r + 1) - (dsum[s][r] - dsum[s][l]);
}
找[l + 1,r]的人移动到 l 层的操作就简化为:

ll go_down(int s,int l,int r){
	return (dsum[s][r] - dsum[s][l]) - (sum[s][r] - sum[s][l]) * l;
}
然后还有一个操作就是,在l + 1和 r 层修建一样的类型的时候,中间的人们的最小花费就是上面一半的人去r + 1层,下面一半的人去 l 层,操作:

ll get_sum(int s,int l,int r){
	int mid = (l + r) >> 1;
	return go_up(s,mid,r) + go_down(s,l,mid);
}
然后开始DP

从最底层 i 层开始,分别令该层修建为 0 或 1,此处设为x类,那么该层的上一层(i + 1)层修建为另一种 y 类

于是令 1 到 i 层的 y 类人全部去往 i + 1 层,然后用 get_sum操作,遍历 1 到 i - 1 的所以 y 类情况去更新最小值

记出 i 层作为 x类时候1 到 i 层这一段 y 类人的最小花费,最后把i 到 n层的 x 类人移动到 i 层,这样第 i 层作为x类的

dp[x][i]就是最优情况了,一直到 n - 1 层就可以

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
#define mem(a,x) memset(a,x,sizeof(a))
#define ll long long
#define maxn 4005
const ll inf = 1e18;

ll a[maxn],b[maxn];
ll dp[2][maxn],sum[2][maxn],dsum[2][maxn];

ll go_up(int s,int l,int r){
	return (sum[s][r] - sum[s][l]) * (r + 1) - (dsum[s][r] - dsum[s][l]);
}

ll go_down(int s,int l,int r){
	return (dsum[s][r] - dsum[s][l]) - (sum[s][r] - sum[s][l]) * l;
}

ll get_sum(int s,int l,int r){
	int mid = (l + r) >> 1;
	return go_up(s,mid,r) + go_down(s,l,mid);
}

int main(){
	int t,n,Case = 1;
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);
		sum[0][0] = sum[1][0] = 0;
		for(int i = 1;i <= n;i++){
			scanf("%lld %lld",&a[i],&b[i]);
			sum[0][i] = sum[0][i - 1] + a[i];
			sum[1][i] = sum[1][i - 1] + b[i];
			dsum[0][i] = dsum[0][i - 1] + a[i] * i;
			dsum[1][i] = dsum[1][i - 1] + b[i] * i;
		}
		ll ans = inf;
		for(int i = 1;i < n;i++){
			dp[0][i] = go_up(1, 0, i);
			dp[1][i] = go_up(0, 0, i);
			for(int j = 1;j < i;j++){
				dp[0][i] = min(dp[0][i],dp[1][j] + get_sum(1,j,i));
				dp[1][i] = min(dp[1][i],dp[0][j] + get_sum(0,j,i));
			}
			ans = min(ans,dp[0][i] + go_down(0,i,n));
			ans = min(ans,dp[1][i] + go_down(1,i,n));
		}
		printf("Case #%d: %lld\n",Case++,ans);
	}
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值