题意大概是:一个王国有N个城市,M条路,都是有向路。现在要求去旅游,不过走的路只能是环(至少也需要有两个城市),即从某个城市开始,从某个城市结束。他们保证这些城市之间的路径都是有环构成的,求至少需要走多少路。
脑洞:乍一看,以为是最短路或者是欧拉路之类的问题。但后来才发现,二分图的一个性质就是每个匹配点的入度和出度都为1(这肯定是匹配之后的)。我们把每一个点拆成两部分,一部分是入读一部分是出度,我们把出度的那些点放入X集合中,把入度的那些点放入Y集合中。
而题意已经说明了所有的点都会被走到,那么说明这道题一定是一个最佳匹配。
然后这里使用的是
O
(
n
3
)
O(n^3)
O(n3)的写法,多了一个slack数组。
然后这道题我们刚开始要把所有的权值都变为负值,最后在转换回来。这样做的原因是,我们的KM算法求出的答案是权值和的最大值。而这道题要求的是最小值。那么我们先把权值都变为负值,求出他权值和的最大值,当我们把他反过来的时候,那便是我们要求的答案,也就是权值和的最小值。
其余的一些解释请看代码的注释。
int w[len][len];
int vis_x[len], vis_y[len], wx[len], wy[len];//x,y的标记数组以及权值数组
int l[len], slack[len];
int N, M, T;
int a, b, c;
void Init(){
//没得到一组数据,我们刚开始要把数据都读进图和x的数组。所以这两个数组我们赋成负的无穷大。
memset(w, -INF, sizeof(w));
memset(wx, -INF, sizeof(wx));
memset(wy, 0, sizeof(wy));
memset(vis_x, 0, sizeof(vis_x));
memset(vis_y, 0, sizeof(vis_y));
memset(l, 0, sizeof(l));
}
int main(){
cin >> T;
while (T--){
Init();
cin >> N >> M;
for (int i=0; i<M; i++){
cin >> a >> b >> c;
w[a][b] = max(w[a][b], -c);
wx[a] = max(wx[a], w[a][b]);
}
int ans = KM();
cout << ans << endl;
}
return 0;
}
int KM(){
for (int i=1; i<=N; i++){
memset(slack, INF, sizeof(slack));//slack数组表示的是每一个x y需要松弛的度数。
while(true){
memset(vis_x, 0, sizeof(vis_x));
memset(vis_y, 0, sizeof(vis_y));
if (find(i)) break;
//先找点,匹配上的话,我们让下一个点去匹配。
//如果匹配不上,我们开始对这个点进行松弛处理。
//slack数组里面存的数据,是让x的权值降到使最近的那个点符合相加等于边长,而这条边不是最长边
int d = INF;
for (int j=1; j<=N; j++){
if (!vis_y[j] && d > slack[j])
d = slack[j];
}//找最小的slack
for (int j=1; j<=N; j++){
if (vis_x[j]) wx[j] = wx[j] - d;
if (vis_y[j]) wy[j] = wy[j] + d;
}
}
}
int sum = 0;
for (int i=1; i<=N; i++)
sum = sum + wx[i] + wy[i];
return -sum;
}
bool find (int p){
vis_x[p] ++;
for (int i=1; i<=N; i++){
if (!vis_y[i] && wx[p]+wy[i] == w[p][i]){
vis_y[i] ++;
if (!l[i] || find(l[i])){
l[i] = p;
return true;
}
}else if (!vis_y[i]) slack[i] = min(slack[i], wx[p]+wy[i]-w[p][i]);
}
return false;
}
KM算法详解+模板
最近又找到一个厉害的博主的厉害的博客。