UVa1336修缮长城

在这里插入图片描述

思路

要想最终代价最低,就不能跳跃着修复,也就是经过一段时间后已经修复好的破损应是一段连续区间。
每次只有两个决策:向左走或者向右走。根据这个可以设计出状态:
dp(i,j,k)表示修好(i,j)后机器人停留在k(0表示在左端,1表示在右端)端的费用。
另外, c 值的总和是固定的费用,不论决策如何,最终都是要加的。因此不用加入状态转移。不过最后不要忘了加上它
修复某处破损的代价虽然不是定值,但却是随着时间线性增长的,所以当修复完一处或一段破损时,修复其他破损的费用可以算出来,只需将其累加到当前状态即可,也可以视作修复某处破损产生的时间代价。

记忆化搜索
#include <cstdio>
#include <cmath> 
#include <cstring>
#include <algorithm>
using namespace std;

const int maxn = 1000 + 5;
const double INF = 1e30;

struct Section {
	double x, c, dt;
	bool operator < (const Section& rhs) const {
		return x < rhs.x;
	}
} s[maxn];

int kase, n;
int vis[maxn][maxn][2];
double v, x, d[maxn][maxn][2];
double sum_dt[maxn]; // prefix sum of dt

double cost(double x1, double x2, int i, int j){
	double finish_dt = 0;
	if(i >= 1&&j >= 1) finish_dt += sum_dt[j] - sum_dt[i-1];
	return (sum_dt[n] - finish_dt) * fabs(x2 - x1)/v;
}

double dp(int i, int j, int k){
	if(i == 1&&j == n) return 0;
	double& ans = d[i][j][k];
	if(vis[i][j][k] == kase) return ans;
	vis[i][j][k] = kase;
	
	double pos = (k == 0? s[i].x : s[j].x);
	ans = INF;
	if(i > 1) ans = min(ans, dp(i-1, j, 0) + cost(pos, s[i-1].x, i, j));
	if(j < n) ans = min(ans, dp(i, j+1, 1) + cost(pos, s[j+1].x, i, j));
	return ans;
}

int main()
{
	memset(vis, 0, sizeof(vis));
	freopen("in.txt","r",stdin);
	while(scanf("%d %lf %lf", &n, &v, &x) == 3 && n){
		++kase;
		double sumc = 0;
	    for(int i = 1; i <= n; i++) {
	        scanf("%lf %lf %lf", &s[i].x, &s[i].c, &s[i].dt);
	        sumc += s[i].c;
	    }
	    sort(s+1, s+n+1); // in increasing order of position
	    
	    sum_dt[0] = 0;
	    for(int i = 1; i <= n; ++i) sum_dt[i] = sum_dt[i-1] + s[i].dt;
	    
	    s[0].x = -INF;
	    s[n+1].x = INF;
	    double ans = INF;
	    
	    for(int i = 1; i <= n+1; ++i){
	    	if(x > s[i-1].x&&x < s[i].x){
	    		if(i > 1) ans = min(ans, dp(i-1, i-1, 0) + cost(x, s[i-1].x, -1, -1));
	    		if(i <= n) ans = min(ans, dp(i, i, 0) + cost(x, s[i].x, -1, -1)); 
	    		break;
			}
		}
	    
	    printf("%d\n",(int)(ans+sumc));
	    //printf("%.0lf\n", floor(ans + sumc));
	}

	return 0;
}

递推:
#include <cstdio>
#include <cmath> 
#include <cstring>
#include <algorithm>
using namespace std;

const int maxn = 1000+5;
const double INF = 1e30;

struct Section {
	double x, c, dt;
	bool operator < (const Section& rhs) const {
		return x < rhs.x;
	}
} s[maxn];

//int vis[maxn][maxn][2];
double d[maxn][maxn][2], x, v;
double sum_dt[maxn]; // prefix sum of dt
int n;

int main()
{
	//freopen("in.txt","r",stdin);
	
	while(scanf("%d%lf%lf", &n, &v, &x) == 3 && n){
		//++kase;
		for(int i = 0;i<=n+1;i++){
			for(int j = 0;j<=n+1;j++){
                d[i][j][0] = INF;
                d[i][j][1] = INF;
            }
		}
            
		double sumc = 0;
	    for(int i = 1; i <= n; i++) {
	        scanf("%lf %lf %lf", &s[i].x, &s[i].c, &s[i].dt);
	        sumc += s[i].c;
	    }
	    s[n+1].x = x; s[n+1].c = s[n+1].dt = 0; // 小技巧,把初始点也加入其中,但设置花费为0
		
	    sort(s+1, s+n+2); // in increasing order of position

	    // 计算前缀和 
	    sum_dt[0] = 0;
	    for(int i = 1; i <= n+1; ++i) sum_dt[i] = sum_dt[i-1] + s[i].dt;
	    
	    // 寻找初始点
	    int start;
		for(int i = 1; i <= n+1; ++i){
			if(s[i].x == x){
				start = i; break;
			}
		} 
		//printf("%d\n",start);
		d[start][start][0] = d[start][start][1] = 0;
		
	    for(int i = start; i >= 1; --i){
	    	for(int j = start; j <= n+1; ++j){
	    		
	    		//double finish_dt = sum_dt[j] - sum_dt[i-1];
	    		double h = (i==1? 0 : sum_dt[i-1]) + sum_dt[n+1] - sum_dt[j];
	    		if(i > 1){ // 向左 
	    			double& ans = d[i-1][j][0];
	    			double cost = d[i][j][0] + ((s[i].x - s[i-1].x) / v) * h ;
	    			ans = min(ans, cost);
	    			cost = d[i][j][1] + ((s[j].x - s[i-1].x) / v) * h;
	    			ans = min(ans, cost);
				}
				if(j < n+1){ // 向右 
	    			double& ans2 = d[i][j+1][1];
	    			double cost = d[i][j][0] + ((s[j+1].x - s[i].x) / v) * h ;
	    			ans2 = min(ans2,cost);
	    			cost = d[i][j][1] + ((s[j+1].x - s[j].x) / v) * h;
	    			ans2 = min(ans2, cost);
				}
			}
		}
	    
	    double ans = min(d[1][n+1][0], d[1][n+1][1]) + sumc;
	    printf("%d\n",(int)(ans));
	}

	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值