题目
传送门 to DarkBZOJ(吐槽:这是我做过的 最糟糕 的题之一)
数据范围与提示
m
−
5
⩽
n
⩽
1
0
4
m-5\leqslant n\leqslant 10^4
m−5⩽n⩽104,边权
w
⩽
1
0
4
w\leqslant 10^4
w⩽104 。小地图
∣
V
∣
⩽
200
|V|\leqslant 200
∣V∣⩽200 但是
∣
E
∣
⩽
10000
|E|\leqslant 10000
∣E∣⩽10000 。
还有一件事:小地图上有重边,std
只保留了最后一条边(其他边正常累加,但是不放入邻接矩阵)。
思路
最初拿到此题,还是感觉挺困难的。因为 “图套图” 听上去就很复杂……
不妨从一个简单的问题开始想:给一个连通图 G G G,求一条回路,使得权值最大。其中,走过偶数次的边不计入权值,走过奇数次的边计算一次。可以想象到,假如只保留 G G G 中走过奇数次的边,那么这些边 构成欧拉回路,但是可能不连通——不连通也没关系,两个连通分量之间的边走两次,就可以接出完整的回路了。
所以,求一条欧拉回路(或欧拉路径,做法类似)要使得走过奇数次的边的边权和最大,就是删掉权值和最小的边集,使得剩下的图的连通分量都存在欧拉回路。
而删边等价于将两个端点的度数奇偶性同时取反,最后使得所有端点度数为偶数。直观理解,删掉一条边可以让 “奇度数” 沿着这条边移动,然后两个 “奇度数” 相撞就会湮灭。所以只需要 “奇度数” 两两匹配,删掉的边权和最小就是最短路啦!
再考虑大地图上的一条边 ⟨ a , b ⟩ \langle a,b\rangle ⟨a,b⟩ 被经过很多遍的情况。可以想到,在小地图上, a → b a\rightarrow b a→b 和 b → a b\rightarrow a b→a 是没有区别的,都是一条路径。所以我们把其中 ⌊ k 2 ⌋ \lfloor{k\over 2}\rfloor ⌊2k⌋ 次经过,理解为 a → b a\rightarrow b a→b,余下的理解为 b → a b\rightarrow a b→a 。那么如果 2 ∣ k 2\mid k 2∣k,走来走去其实只走了一个 a → a a\rightarrow a a→a 的环,而且我们知道,不经过 a a a 也可以(连通分量之间的连接);如果 2 ∤ k 2\nmid k 2∤k,走来走去其实走了一个 a → b a\rightarrow b a→b 的路径。看来我们只需要求出这两种方案的权值。
对于第一种,相当于删边使得小地图存在欧拉回路,直接把小地图中唯二的 “奇度数” 之间的最短路删掉;对于第二种,我们走出的路径是 a → b a\rightarrow b a→b,所以要删边使得 a , b a,b a,b 度数为奇数、其余为偶数。还是两两匹配,其实也就三种情况(就看 a a a 跟谁匹配,剩下两个就一组)。
那么大地图上一条边可以被经过多少次?其实跟最初的简化问题一样:大地图上每条边的经过次数要使得大地图存在欧拉回路。
大地图上一条边的权值,只跟其经过次数奇偶性有关,而判定欧拉回路也是如此。所以只需要考虑每条边是走过偶数次还是走过奇数次。当每条边都经过偶数次时,大地图已经没有 “奇度数” 了,所以想要让一条边走过奇数次,必须是 “奇度数” 自行湮灭,也就是选出若干个环(边不相交),环上的边都只走过奇数次。
显然可以无源汇最小费用循环流。然而 m ⩽ n + 5 m\leqslant n+5 m⩽n+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;
}