[COCI2013]hiperprostor dij+DP+凸包

Description
给n个点,m条边的无向图,有两种边,一种是x边,x的值由你来选择,一种是普通边给出权值。给10组询问每次询问x~y最短路的可能性有多少条,并求出它们的和。


Sample Input
4 4
1 2 x
2 3 x
3 4 x
1 4 8
3
2 1
1 3
1 4


Sample Output
0 0
inf
3 17


10组询问单组来做。
首先,可以想到做一个DP。
f[i][j]表示到达i这个点,经过j条x边的最短路。
然后呢,你想假设你选择一条经过j个x边的路径,他的长度就是f[ed][j]+j*x
然后这是条直线,你就求个凸包即可。
答案统计什么的好像有点细节啊,挺麻烦的。。。


#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
typedef long long LL;

struct node {
	int x, k, d;
	friend bool operator < (node a, node b) {return a.d > b.d;}
}; priority_queue<node> q;
struct edge {
	int x, y, c, next, id;
} e[11000]; int len, last[11000];
int yp, st, ed, sta[510], b[510];
int n; bool v[510][510]; LL f[510][510];
char ss[10];

void ins(int x, int y, int c, int id) {
	e[++len].x = x; e[len].y = y; e[len].c = c; e[len].id = id;
	e[len].next = last[x]; last[x] = len;
}

void dij() {
	memset(f, 63, sizeof(f));
	memset(v, 0, sizeof(v));
	f[st][0] = 0;
	node tmp; tmp.x = st, tmp.d = 0, tmp.k = 0;
	q.push(tmp);
	while(!q.empty()) {
		node now = q.top(); q.pop();
		int x = now.x, i = now.k;
		if(v[x][i]) continue; v[x][i] = 1;
		for(int k = last[x]; k; k = e[k].next) {
			int y = e[k].y;
			int o = i + e[k].id; if(o > n) continue;
			if(f[y][o] > f[x][i] + e[k].c) {
				f[y][o] = f[x][i] + e[k].c;
				if(!v[y][o]) tmp.x = y, tmp.d = f[y][o], tmp.k = o, q.push(tmp);
			}
		}
	}
}

double lv(int x, int y) {
	return (double)(f[ed][y] - f[ed][x]) / (x - y);
}
int cs(int x, int y) {
	double hh = f[ed][y] - f[ed][x];
	if(hh <= 0) return 0;
	return (hh - 1) / (x - y);
}

int main() {
	int m; scanf("%d%d", &n, &m);
	for(int i = 1; i <= m; i++) {
		int x, y; scanf("%d%d", &x, &y);
		scanf("%s", ss + 1);
		if(ss[1] == 'x') ins(x, y, 0, 1);
		else {
			int s = 0;
			for(int j = 1; j <= strlen(ss + 1); j++) s = s * 10 + ss[j] - '0';
			ins(x, y, s, 0);
		}
	}
	int q; scanf("%d", &q);
	while(q--) {
		st, ed; scanf("%d%d", &st, &ed);
		dij(); bool bk = 0;
		for(int i = 0; i < n; i++) if(f[ed][i] < 999999999) bk = 1;
		if(!bk) {puts("0 0"); continue;}
		if(f[ed][0] > 999999999) {puts("inf"); continue;}
		int tp = 0;
		for(int i = n - 1; i >= 0; i--) if(f[ed][i] < 999999999){
			while(tp > 1 && lv(sta[tp - 1], sta[tp]) >= lv(sta[tp], i)) tp--;
			sta[++tp] = i;
		} LL ans1 = 1, ans2 = f[ed][0];
		for(int i = 1; i < tp; i++) {
			b[i] = cs(sta[i], sta[i + 1]);
			int l = b[i - 1] + 1, r = b[i];
			if(l < 1) l = 1;
			if(r < l) continue;
			ans1 += r - l + 1;
			ans2 += ((LL)(r + l) * sta[i] + 2LL * f[ed][sta[i]]) * (r - l + 1) / 2;
		} printf("%lld %lld\n", ans1, ans2);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值