[ZJOI2016]旅行者(网格图分治最短路)

problem

luogu-P3350

solution

据说,网格图最短路用分治是一个人人皆知的套路对不起我不是人

类比整体二分的算法流程。

考虑在一个 ( x l , y l ) − ( y l , y r ) (xl,yl)-(yl,yr) (xl,yl)(yl,yr) 矩阵内处理 [ q l , q r ] [ql,qr] [ql,qr] 的询问。

以矩阵的中界线 mid \text{mid} mid 将矩阵划成两半,显然哪一维更长划哪一维。

以中界线上的每一个点为起点跑一遍最短路,然后更新所有需要跨过这条线的询问的答案,相当于是强制路线必须经过该点。

如果不需要跨过这条线,即询问的起终点均在线的某一侧,就分成 l , r l,r l,r 两个部分,继续分治下去。

时间复杂度好像都说是 O ( N N log ⁡ N ) O(N\sqrt{N}\log N) O(NN logN)

具体见代码即可明白。

code

#include <bits/stdc++.h>
using namespace std;
#define maxn 20005
#define maxq 100005
#define Pair pair < int, int >
struct node { int u, v, id; }q[maxq], l[maxq], r[maxq];
vector < Pair > G[maxn];
priority_queue < Pair, vector < Pair >, greater < Pair > > que;
int x[maxn], y[maxn], dis[maxn], ans[maxq];
int n, m, Q;

int id( int i, int j ) { return (i - 1) * m + j; }

void dijkstra( int s, int xl, int xr, int yl, int yr ) {
	for( int i = xl;i <= xr;i ++ )
		for( int j = yl;j <= yr;j ++ )
			dis[id(i, j)] = 0x7f7f7f7f;
	que.push( make_pair( dis[s] = 0, s ) );
	while( ! que.empty() ) {
		int u = que.top().second, w = que.top().first;
		que.pop();
		if( dis[u] ^ w ) continue;
		for( int i = 0;i < G[u].size();i ++ ) {
			int v = G[u][i].first; w = G[u][i].second;
			if( x[v] < xl or x[v] > xr or y[v] < yl or y[v] > yr ) continue;
			if( dis[v] > dis[u] + w )
				que.push( make_pair( dis[v] = dis[u] + w, v ) );
		}
	}
}

void solve( int ql, int qr, int xl, int xr, int yl, int yr ) {
	if( ql > qr or xl > xr or yl > yr ) return;
	int cntl = 0, cntr = 0;
	if( xr - xl >= yr - yl ) {
		int mid = xr + xl >> 1;
		for( int i = yl;i <= yr;i ++ ) {
			dijkstra( id(mid, i), xl, xr, yl, yr );
			for( int j = ql;j <= qr;j ++ )
				ans[q[j].id] = min( ans[q[j].id], dis[q[j].u] + dis[q[j].v] );
		}
		for( int i = ql;i <= qr;i ++ ) {
			if( x[q[i].u] < mid and x[q[i].v] < mid ) l[++ cntl] = q[i];
			if( x[q[i].u] > mid and x[q[i].v] > mid ) r[++ cntr] = q[i];
		}
		for( int i = 1;i <= cntl;i ++ ) q[ql + i - 1] = l[i];
		for( int i = 1;i <= cntr;i ++ ) q[ql + cntl + i - 1] = r[i];
		solve( ql, ql + cntl - 1, xl, mid - 1, yl, yr );
		solve( ql + cntl, ql + cntl + cntr - 1, mid + 1, xr, yl, yr );
	}
	else {
		int mid = yr + yl >> 1;
		for( int i = xl;i <= xr;i ++ ) {
			dijkstra( id(i, mid), xl, xr, yl, yr );
			for( int j = ql;j <= qr;j ++ )
				ans[q[j].id] = min( ans[q[j].id], dis[q[j].u] + dis[q[j].v] );
		}
		for( int i = ql;i <= qr;i ++ ) {
			if( y[q[i].u] < mid and y[q[i].v] < mid ) l[++ cntl] = q[i];
			if( y[q[i].u] > mid and y[q[i].v] > mid ) r[++ cntr] = q[i];
		}
		for( int i = 1;i <= cntl;i ++ ) q[ql + i - 1] = l[i];
		for( int i = 1;i <= cntr;i ++ ) q[ql + cntl + i - 1] = r[i];
		solve( ql, ql + cntl - 1, xl, xr, yl, mid - 1 );
		solve( ql + cntl, ql + cntl + cntr - 1, xl, xr, mid + 1, yr );
	}
}

int main() {
	scanf( "%d %d", &n, &m );
	for( int i = 1;i <= n;i ++ )
		for( int j = 1, x;j < m;j ++ ) {
			scanf( "%d", &x );
			G[id(i, j)].push_back( make_pair( id(i, j + 1), x ) );
			G[id(i, j + 1)].push_back( make_pair( id(i, j), x ) );
		}
	for( int i = 1;i < n;i ++ )
		for( int j = 1, x;j <= m;j ++ ) {
			scanf( "%d", &x );
			G[id(i, j)].push_back( make_pair( id(i + 1, j), x ) );
			G[id(i + 1, j)].push_back( make_pair( id(i, j), x ) );
		}
	for( int i = 1;i <= n;i ++ )
		for( int j = 1;j <= m;j ++ )
			x[id(i, j)] = i, y[id(i, j)] = j;
	scanf( "%d", &Q );
	for( int i = 1, a1, b1, a2, b2;i <= Q;i ++ ) {
		scanf( "%d %d %d %d", &a1, &b1, &a2, &b2 );
		q[i] = (node){ id(a1, b1), id(a2, b2) }, q[i].id = i;
	}
	memset( ans, 0x7f, sizeof( ans ) );
	solve( 1, Q, 1, n, 1, m );
	for( int i = 1;i <= Q;i ++ ) printf( "%d\n", ans[i] );
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值