NOIP模拟(11.03)T3 边的处理

边的处理

题目背景:

11.03 NOIP模拟T3

分析:分治

 

感觉很妙妙的一道题,首先我们发现,因为每一条边只要在询问范围内,就一定要被使用,那么我们就可以发现,这是可以合并的,比如说一种从x经过i ~ j的所有边然后到达y的方案,和一个从y出发经过j + 1 ~ k的所有边最后到达z的方案可以合并成为一个从x出发经过i ~ k的所有边最后到达z的一个方案,那么,我们可以选择尽可能多的区间来进行合并会显得非常稳,那么考虑如何有效的分割区间,这个时候我们就可以想到分治了,我们先把询问离线,然后用对应的边集进行处理,假设我们现在的边集是l ~ rmid = (l + r) / 2,那么我们可以通过处理从l ~ mid当中任意一条边开始一直走到第mid条边的方案数,和从mid + 1开始走到mid + 1 ~ r当中的任意一条边的方案数,然后就可以处理出所有询问的边集跨过了中间第mid条边的询问,直接通过枚举中间点,来合并两边的方案,找到最优的方案就可以了。具体我们定义left[i][j][k]表示,走完第i条边 ~ mid条边,从à j的最小代价,right[i][j][k]表示,走完第mid + 1条边到第i条边,从à k的最小代价,然后对于询问直接可以枚举中间点j,考虑如何得到rightleft,我们只需要转移的时候考虑是走过第i条边,还是不走第i条边,还是没有办法到达当前状态即可,复杂度应该是O(qn + qlogm + mlogm * n2),具体详见代码

Source:

/*
	created by scarlyw
*/
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <string>
#include <cstring>
#include <cctype>
#include <vector>
#include <queue>
#include <set>
#include <ctime>

const int MAXN = 30 + 5;
const int MAXM = 20000 + 10;
const int MAXQ = 200000 + 10;
const int INF = 1000000000;

struct query {
	int u, v, l, r, id;
} q[MAXQ], temp[MAXQ];

struct edge {
	int x, y, r, c;
} e[MAXM];

int n, m, t;
int ans[MAXQ];
int left[MAXM][MAXN][MAXN], right[MAXM][MAXN][MAXN];

inline void solve(int l, int r, int ql, int qr) {
	if (ql > qr) return ;
	if (l == r) {
		for (int i = ql; i <= qr; ++i) {
			ans[q[i].id] = INF;
			if ((q[i].u == e[l].x && q[i].v == e[l].y) || 
				q[i].u == e[l].y && q[i].v == e[l].x)
					ans[q[i].id] = e[l].c;
			if (q[i].u == q[i].v) ans[q[i].id] = std::min(ans[q[i].id], e[l].r);
			(ans[q[i].id] == INF) ? (ans[q[i].id] = -1) : 0;
		}
		return ;
	}
	int mid = l + r >> 1;
	for (int i = 1; i <= n; ++i) 
		for (int j = 1; j <= n; ++j) 
			left[mid + 1][i][j] = right[mid][i][j] = INF;
	for (int i = 1; i <= n; ++i)
		left[mid + 1][i][i] = right[mid][i][i] = 0;
	for (int i = mid; i >= l; --i) {
		for (int u = 1; u <= n; ++u)
			for (int v = 1; v <= n; ++v)
				left[i][u][v] = left[i + 1][u][v] + e[i].r;
		for (int u = 1; u <= n; ++u) {
			if (left[i + 1][u][e[i].x] < INF)
				left[i][u][e[i].y] = std::min(left[i][u][e[i].y], 
					left[i + 1][u][e[i].x] + e[i].c);
			if (left[i + 1][u][e[i].y] < INF)
				left[i][u][e[i].x] = std::min(left[i][u][e[i].x], 
					left[i + 1][u][e[i].y] + e[i].c);
		}
	}
	for (int i = mid + 1; i <= r; ++i) {
		for (int u = 1; u <= n; ++u)
			for (int v = 1; v <= n; ++v)
				right[i][u][v] = right[i - 1][u][v] + e[i].r;
		for (int u = 1; u <= n; ++u) {
			if (right[i - 1][u][e[i].x] < INF)
				right[i][u][e[i].y] = std::min(right[i][u][e[i].y], 
					right[i - 1][u][e[i].x] + e[i].c);
			if (right[i - 1][u][e[i].y] < INF)
				right[i][u][e[i].x] = std::min(right[i][u][e[i].x], 
					right[i - 1][u][e[i].y] + e[i].c);
		}
	}
	for (int i = ql; i <= qr; ++i) {
		if (q[i].l <= mid && q[i].r > mid) {
			int val = INF;
			for (int k = 1; k <= n; ++k)
				val = std::min(val, left[q[i].l][k][q[i].u] + 
					right[q[i].r][k][q[i].v]);
			ans[q[i].id] = (val == INF) ? -1 : val;
		}
	}
	int new_l = ql - 1, new_r = qr + 1;
	for (int i = ql; i <= qr; ++i) temp[i] = q[i];
	for (int i = ql; i <= qr; ++i) if (temp[i].r <= mid) q[++new_l] = temp[i];
	for (int i = qr; i >= ql; --i) if (temp[i].l > mid) q[--new_r] = temp[i];
	solve(l, mid, ql, new_l), solve(mid + 1, r, new_r, qr);
}

inline void read_in() {
	scanf("%d%d%d", &n, &m, &t);
	for (int i = 1; i <= m; ++i) 
		scanf("%d%d%d%d", &e[i].x, &e[i].y, &e[i].c, &e[i].r);
	for (int i = 1; i <= t; ++i) 
		scanf("%d%d%d%d", &q[i].u, &q[i].v, &q[i].l, &q[i].r), q[i].id = i;
}

inline void solve() {
	solve(1, m, 1, t);
	for (int i = 1; i <= t; ++i) std::cout << ans[i] << "\n";
}

int main() {
	read_in();
	solve();
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值