[DarkBZOJ2887]旅行

122 篇文章 0 订阅
本文介绍了如何解决图论中的一个复杂问题,即在一个图中找到一条具有最大权值的路径,同时涉及奇数次经过的边。作者首先简化问题,提出寻找奇数次经过边的最大权值回路,并通过删除最小权值边来实现。接着,作者讨论了大图和小图的概念,以及如何处理边的重复经过。解决方案涉及到最小费用循环流和DFS树的构建。文章最后给出了详细的算法实现,并提供了代码示例。
摘要由CSDN通过智能技术生成

题目

传送门 to DarkBZOJ(吐槽:这是我做过的 最糟糕 的题之一)

数据范围与提示
m − 5 ⩽ n ⩽ 1 0 4 m-5\leqslant n\leqslant 10^4 m5n104,边权 w ⩽ 1 0 4 w\leqslant 10^4 w104 。小地图 ∣ V ∣ ⩽ 200 |V|\leqslant 200 V200 但是 ∣ E ∣ ⩽ 10000 |E|\leqslant 10000 E10000

还有一件事:小地图上有重边,std 只保留了最后一条边(其他边正常累加,但是不放入邻接矩阵)。

思路

最初拿到此题,还是感觉挺困难的。因为 “图套图” 听上去就很复杂……

不妨从一个简单的问题开始想:给一个连通图 G G G,求一条回路,使得权值最大。其中,走过偶数次的边不计入权值,走过奇数次的边计算一次。可以想象到,假如只保留 G G G 中走过奇数次的边,那么这些边 构成欧拉回路,但是可能不连通——不连通也没关系,两个连通分量之间的边走两次,就可以接出完整的回路了。

所以,求一条欧拉回路(或欧拉路径,做法类似)要使得走过奇数次的边的边权和最大,就是删掉权值和最小的边集,使得剩下的图的连通分量都存在欧拉回路。

而删边等价于将两个端点的度数奇偶性同时取反,最后使得所有端点度数为偶数。直观理解,删掉一条边可以让 “奇度数” 沿着这条边移动,然后两个 “奇度数” 相撞就会湮灭。所以只需要 “奇度数” 两两匹配,删掉的边权和最小就是最短路啦!

再考虑大地图上的一条边 ⟨ a , b ⟩ \langle a,b\rangle a,b 被经过很多遍的情况。可以想到,在小地图上, a → b a\rightarrow b ab b → a b\rightarrow a ba 是没有区别的,都是一条路径。所以我们把其中 ⌊ k 2 ⌋ \lfloor{k\over 2}\rfloor 2k 次经过,理解为 a → b a\rightarrow b ab,余下的理解为 b → a b\rightarrow a ba 。那么如果 2 ∣ k 2\mid k 2k,走来走去其实只走了一个 a → a a\rightarrow a aa 的环,而且我们知道,不经过 a a a 也可以(连通分量之间的连接);如果 2 ∤ k 2\nmid k 2k,走来走去其实走了一个 a → b a\rightarrow b ab 的路径。看来我们只需要求出这两种方案的权值。

对于第一种,相当于删边使得小地图存在欧拉回路,直接把小地图中唯二的 “奇度数” 之间的最短路删掉;对于第二种,我们走出的路径是 a → b a\rightarrow b ab,所以要删边使得 a , b a,b a,b 度数为奇数、其余为偶数。还是两两匹配,其实也就三种情况(就看 a a a 跟谁匹配,剩下两个就一组)。

那么大地图上一条边可以被经过多少次?其实跟最初的简化问题一样:大地图上每条边的经过次数要使得大地图存在欧拉回路。

大地图上一条边的权值,只跟其经过次数奇偶性有关,而判定欧拉回路也是如此。所以只需要考虑每条边是走过偶数次还是走过奇数次。当每条边都经过偶数次时,大地图已经没有 “奇度数” 了,所以想要让一条边走过奇数次,必须是 “奇度数” 自行湮灭,也就是选出若干个环(边不相交),环上的边都只走过奇数次。

显然可以无源汇最小费用循环流。然而 m ⩽ n + 5 m\leqslant n+5 mn+5,可以建立 d f s \tt dfs dfs 树后,枚举非树边走过奇数次或偶数次,则树边可以唯一确定。甚至不需要树剖等,直接 O ( n ) \mathcal O(n) O(n) 暴力扫描 d f s \tt dfs dfs 树即可。

代码

#include <cstdio> // XJX yyds!!!
#include <iostream>
#include <vector>
#include <cstring>
#include <algorithm>
#include <cctype>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
	int a = 0, c = getchar(), f = 1;
	for(; !isdigit(c); c=getchar())
		if(c == '-') f = -f;
	for(; isdigit(c); c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
void writeint(int x){
	if(x > 9) writeint(x/10);
	putchar(char((x%10)^48));
}

const int MAXN = 10015;
struct Edge{
	int to, nxt;
	Edge() = default;
	Edge(int _to,int _nxt):to(_to),nxt(_nxt){ }
};
Edge e[MAXN<<1];
int head[MAXN], cntEdge;
void addEdge(int a,int b){
	e[cntEdge] = Edge(b,head[a]);
	head[a] = cntEdge ++;
}

bool bad[MAXN], vis[MAXN];
void build(int x,int pre){
	vis[x] = true;
	for(int i=head[x]; ~i; i=e[i].nxt)
		if((i^1) != pre){
			if(vis[e[i].to])
				bad[i>>1] = true;
			else build(e[i].to,i);
		}
}

int val[MAXN]; ///< value on edge
int val0; ///< if go twice
bool tag[MAXN];
llong scan(int x,int pre){
	llong res = 0;
	for(int i=head[x]; ~i; i=e[i].nxt)
		if(e[i].to != pre && !bad[i>>1]){
			res += scan(e[i].to,x);
			tag[x] ^= tag[e[i].to];
			if(!tag[e[i].to]) res += val0;
			else res += val[i>>1];
		}
	return res;
}

const int INFTY = 0x3fffffff;
const int WXK = 205;
int g[WXK][WXK], ys[MAXN], deg[WXK];
int tmp[WXK]; // edges not on tree
int main(){
	int nbig = readint(), mbig = readint();
	int nsmal = readint(), msmal = readint();
	rep(i,1,nbig) ys[i] = readint();
	memset(head+1,-1,nbig<<2);
	for(int a,b,i=1; i<=mbig; ++i){
		a = readint(), b = readint();
		addEdge(a,b), addEdge(b,a);
	}
	build(1,-1); int tot = 0;
	for(int i=0; i!=mbig; ++i)
		if(bad[i]) tmp[tot ++] = i;
	int sum = 0; ///< sum of edges on small graph
	for(int a,b,w; msmal; --msmal){
		a = readint(), b = readint();
		sum += (w = readint());
		g[a][b] = g[b][a] = w;
		++ deg[a], ++ deg[b];
	}
	rep(i,1,nsmal) rep(j,1,nsmal)
		if(!g[i][j] && i != j) g[i][j] = INFTY;
	int odd[2], cnt_odd = 0;
	rep(i,1,nsmal) if(deg[i]&1) odd[cnt_odd ++] = i;
	if(!cnt_odd) return printf("%lld\n",llong(mbig)*sum), 0;
	rep(k,1,nsmal) rep(i,1,nsmal) rep(j,1,nsmal)
		g[i][j] = min(g[i][j],g[i][k]+g[k][j]);
	val0 = sum-g[odd[0]][odd[1]];
	rep(i,1,nbig) for(int j=head[i]; ~j; j=e[j].nxt)
		if(j&1) val[j>>1] = sum-min(
			g[ys[i]][odd[0]]+g[ys[e[j].to]][odd[1]],min(
			g[ys[i]][odd[1]]+g[ys[e[j].to]][odd[0]],
			g[ys[i]][ys[e[j].to]]+g[odd[0]][odd[1]]
		));
	llong ans = 0;
	for(int S=0; S!=(1<<tot); ++S){
		memset(tag+1,false,nbig);
		llong now = 0;
		for(int i=0; i!=tot; ++i)
			if(S>>i&1){
				const int &id = tmp[i];
				tag[e[id<<1].to] ^= 1;
				tag[e[id<<1|1].to] ^= 1;
				now += val[id];
			}
			else now += val0;
		ans = max(ans,now+scan(1,-1));
	}
	printf("%lld\n",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值