51Nod - 1781 dp + 线段树 + 离散化

题意:

Pinball的游戏界面由m+2行、n列组成。第一行在顶端。一个球会从第一行的某一列出发,开始垂直下落,界面上有一些漏斗,一共有m个漏斗分别放在第2~m+1行,第i个漏斗的作用是把经过第i+1行且列数在Ai~Bi之间的球,将其移到下一行的第Ci列。 使用第i个漏斗需要支付Di的价钱,你需要保留一些漏斗使得球无论从第一行的哪一列开始放,都只可能到达第m+2行的唯一 一列,求花费的最少代价。

(样例的图)

(我们保留2,4,5即可,代价为5+3+12=20)

Input
第一行两个数,m和n。m<=100000,2<=n<=1000000000
接下来m行,第i+1行描述第i个漏斗的属性,Ai,Bi,Ci,Di (1<=Ai<=Ci<=Bi<=n, 1<=Di<=1000000000)。
Output
若不存在一种方案能满足条件则输出-1,否则输出最小花费
Input示例
5 6
3 5 4 8
1 4 3 5
4 6 5 7
5 6 5 3
3 5 4 12
Output示例
20

思路:

好题,一开始感觉是离散化线段树,但是想不出来怎么dp,看了别人的思路才恍然大悟。
这道题的关键在于要看出来最后只需要处理从1列出发的以及从n列出发的球能否到达同一列即可。
因为对于假设a[i]是第i列的球最终能到达的列数,那么如果i < j,则必定a[i] <= a[j],画一画就能看出来,这样的话,如果第1列的球最终能到达第x列,而且第n列的球最终也能到达第x列,那1到n之间的球都能到达x列。所以题目所要求的其实就是看1列和n列是否满足而已。
这里设dp1[i]为第1列到到达第i个漏斗的最小代价,dpn[i]为第n列到达第i个漏斗的最小代价,最终答案就是min(dp1[i] + dpn[i] - a[i].v)。最后减掉的a[i].v是dp1[i]和dpn[i]重复算了两次的,另外,不用担心在这之前会存在1列的路径上和n列的路径上的代价有重复的情况,因为如果发生这种情况,说明1列和n列的球在之前已经到达同一个漏斗,这样的结果一定比当前计算的结果更优。
知道这个状态方程,剩下的很显然要有线段树优化,另外要注意每个漏斗的a,b,c都要进行离散化,1和n也必须要进行离散化。

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1
typedef long long ll;
const int MAXN = 3e5 + 10;
const ll INF = 0x3f3f3f3f3f3f3f3fLL;

ll minx[MAXN << 2]; 

void pushup(int rt) {
	minx[rt] = min(minx[rt << 1], minx[rt << 1 | 1]);  
}

void build(int l, int r, int rt) {
	minx[rt] = INF;
	if (l == r) return;
	int m = (l + r) >> 1;
	build(lson);
	build(rson);
	pushup(rt);
}

ll query(int L, int R, int l, int r, int rt) {
	if (L <= l && r <= R) return minx[rt];
	int m = (l + r) >> 1;
	ll res = INF;
	if (L <= m) res = min(res, query(L, R, lson));
	if (R > m) res = min(res, query(L, R, rson));
	return res;
}

void update(int pos, ll val, int l, int r, int rt) {
	if (l == r) {
		minx[rt] = min(minx[rt], val);
		return;
	}
	int m = (l + r) >> 1;
	if (pos <= m) update(pos, val, lson);
	else update(pos, val, rson);
	pushup(rt);
}

struct node {
	int l, r, c, v;
} a[MAXN];

int discre(int m, int n) {
	vector <int> vec;
	vec.push_back(1); vec.push_back(n);
	for (int i = 1; i <= m; i++) {
		vec.push_back(a[i].l); vec.push_back(a[i].r); vec.push_back(a[i].c);
	}
	sort(vec.begin(), vec.end());
	vec.erase(unique(vec.begin(), vec.end()), vec.end());
	for (int i = 1; i <= m; i++) {
		a[i].l = lower_bound(vec.begin(), vec.end(), a[i].l) - vec.begin() + 1;
		a[i].r = lower_bound(vec.begin(), vec.end(), a[i].r) - vec.begin() + 1;
		a[i].c = lower_bound(vec.begin(), vec.end(), a[i].c) - vec.begin() + 1;
	}
	return lower_bound(vec.begin(), vec.end(), n) - vec.begin() + 1;
}

ll dp1[MAXN], dpn[MAXN];

int main() {
	int m, n;
	scanf("%d%d", &m, &n);
	for (int i = 1; i <= m; i++) 
		scanf("%d%d%d%d", &a[i].l, &a[i].r, &a[i].c, &a[i].v);
	if (n == 1) {
		puts("0");
		return 0;
	}
	n = discre(m, n);		// 更新离散化之后的n
	build(1, n, 1);
	for (int i = 1; i <= m; i++) {
		dp1[i] = a[i].l <= 1 ? a[i].v : INF;
		dp1[i] = min(dp1[i], query(a[i].l, a[i].r, 1, n, 1) + a[i].v);
		if (dp1[i] != INF) update(a[i].c, dp1[i], 1, n, 1);
	}
	build(1, n, 1);
	for (int i = 1; i <= m; i++) {
		dpn[i] = a[i].r >= n ? a[i].v : INF;
		dpn[i] = min(dpn[i], query(a[i].l, a[i].r, 1, n, 1) + a[i].v);
		if (dpn[i] != INF) update(a[i].c, dpn[i], 1, n, 1);
	}
	ll ans = INF;
	for (int i = 1; i <= m; i++) {
		//printf("%d   %d   %d\n", a[i].l, a[i].r, a[i].c);
		//printf("%lld   %lld\n", dp1[i], dpn[i]);
		ans = min(ans, dp1[i] + dpn[i] - a[i].v);
	}
	if (ans == INF) puts("-1");
	else printf("%lld\n", ans);
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值