【BZOJ2407】探险(建图,多点最短路)

题面

探险家小T好高兴!X国要举办一次溶洞探险比赛,获奖者将得到丰厚奖品哦!小T虽然对奖品不感兴趣,但是这个大振名声的机会当然不能错过!
比赛即将开始,工作人员说明了这次比赛的规则:每个溶洞和其他某些溶洞有暗道相连。两个溶洞之间可能有多条道路,也有可能没有,但没有一条暗道直接从自己连到自己。参赛者需要统一从一个大溶洞出发,并再次回到这个大溶洞。
如果就这么点限制,那么问题就太简单了,可是举办方又提出了一个条件:不能经过同一条暗道两次。这个条件让大家犯难了。这该怎么办呢?
到了大溶洞口后,小T愉悦地发现这个地方他曾经来过,他还记得有哪些暗道,以及通过每条暗道的时间。小T现在向你求助,你能帮他算出至少要多少时间才能回到大溶洞吗?

笔者注:大溶洞口是 1 号点。

Input

第一行两个数n,m表示溶洞的数量以及暗道的数量。

接下来m行,每行4个数s、t、w、v,表示一个暗道连接的两个溶洞s、t,这条暗道正着走(s → t)的所需要的时间w,倒着走(t → s)所需要的时间v。由于溶洞的相对位置不同,w与v可能不同。

Output

输出一行一个数t,表示最少所需要的时间。

样例输入

3 3
1 2 2 1
2 3 4 5
3 1 3 2

样例输出

8

数据范围

n ≤ 10000 , m ≤ 200000 , 1 ≤ w , v ≤ 10000 n\leq 10000,m\leq200000,1\leq w,v\leq 10000 n10000,m200000,1w,v10000

题解

如果是两个不同点的最短路的话,是无论如何不会走同一条路径两次的(边权为正)。

所以我们最好把这个问题转化为不同点之间的最短路。

这样一来,我们发现一条最短路径合法,当且仅当它的开头边和结尾边不相同。

那么,我们就把 1 号点根据每条邻接边拆开,保证拆点后每条邻接边都对应一个唯一的“1 号点”。

答案就是这些 “1 号点” 两两之间的最短路。

一个点集中两两最短路是个经典问题,两种解决方案可以参考「神」OneInDark 的博客旅行者:多源点最短路。

这里我使用时间复杂度更劣但是更好懂的第二种做法:二进制分组。

就是按照二进制每一位是否是 1 把点集划分为 O ( log ⁡ ) O(\log) O(log) 对集合,每对点集之间求最短路。

时间复杂度 O ( m log ⁡ 2 m ) O(m\log^2 m) O(mlog2m)

CODE

这次叕把 m 写成 n 了

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define MAXN 210005
#define LL long long
#define ULL unsigned long long
#define ENDL putchar('\n')
#define DB double
#define lowbit(x) (-(x) & (x))
#define FI first
#define SE second
int xchar() {
	static const int maxn = 1000000;
	static char b[maxn];
	static int pos = 0,len = 0;
	if(pos == len) pos = 0,len = fread(b,1,maxn,stdin);
	if(pos == len) return -1;
	return b[pos ++];
}
//#define getchar() xchar()
LL read() {
	LL f = 1,x = 0;int s = getchar();
	while(s < '0' || s > '9') {if(s<0)return -1;if(s=='-')f=-f;s = getchar();}
	while(s >= '0' && s <= '9') {x = (x<<1) + (x<<3) + (s^48);s = getchar();}
	return f*x;
}
void putpos(LL x) {if(!x)return ;putpos(x/10);putchar((x%10)^48);}
void putnum(LL x) {
	if(!x) {putchar('0');return ;}
	if(x<0) putchar('-'),x = -x;
	return putpos(x);
}
void AIput(LL x,int c) {putnum(x);putchar(c);}

int n,m,s,o,k;
int hd[MAXN],nx[MAXN<<1],v[MAXN<<1],w[MAXN<<1],cne;
void ins(int x,int y,int z) {
	nx[++ cne] = hd[x]; v[cne] = y; hd[x] = cne; w[cne] = z;
}
int nn;
LL dp[MAXN];
int mg(int a,int b) {return !a||!b ? (a+b):(dp[a]<dp[b] ? a:b);}
int tre[MAXN<<1];
void upd(int x,int y) {
	tre[nn+x] = y;
	for(int s=(nn+x)>>1;s;s>>=1) tre[s] = mg(tre[s<<1],tre[s<<1|1]);
}
int main() {
	nn = n = read(); m = read();
	for(int i = 1;i <= m;i ++) {
		s = read();o = read();
		int k1 = read(),k2 = read();
		if(s > o) swap(s,o),swap(k1,k2);
		if(s == 1) s = ++ nn;
		ins(s,o,k1); ins(o,s,k2);
	}
	LL ans = 1e18;
	for(int bt = 1<<17;bt > 0;bt >>= 1) {
		vector<int> a1,a2;
		int cn1 = 0,cn2 = 0;
		for(int i = 1;i <= nn-n;i ++) {
			if(i & bt) a1.push_back(i+n),cn1 ++;
			else a2.push_back(i+n),cn2 ++;
		}
		if(cn1 && cn2) {
			dp[0] = 1e18; tre[0] = 0;
			for(int i = 1;i <= nn;i ++) tre[i] = tre[nn+i] = 0,dp[i] = 1e18;
			for(int i = 0;i < cn1;i ++) dp[a1[i]] = 0,upd(a1[i],a1[i]);
			for(int i = 1;i < nn;i ++) {
				int t = tre[1]; if(!t) break;
				for(int j = hd[t];j;j = nx[j]) {
					if(dp[t]+w[j] < dp[v[j]]) {
						dp[v[j]] = dp[t] + w[j];
						upd(v[j],v[j]);
					}
				} upd(t,0);
			} for(int i = 0;i < cn2;i ++) ans = min(ans,dp[a2[i]]);
			dp[0] = 1e18; tre[0] = 0;
			for(int i = 1;i <= nn;i ++) tre[i] = tre[nn+i] = 0,dp[i] = 1e18;
			for(int i = 0;i < cn2;i ++) dp[a2[i]] = 0,upd(a2[i],a2[i]);
			for(int i = 1;i < nn;i ++) {
				int t = tre[1]; if(!t) break;
				for(int j = hd[t];j;j = nx[j]) {
					if(dp[t]+w[j] < dp[v[j]]) {
						dp[v[j]] = dp[t] + w[j];
						upd(v[j],v[j]);
					}
				} upd(t,0);
			} for(int i = 0;i < cn1;i ++) ans = min(ans,dp[a1[i]]);
		}
	}
	AIput(ans,'\n');
	return 0;
}

你可能感兴趣的:(图论,C++,算法,图论,最短路,二进制分组)