之前在网上搜了一个下午没搜到这道题的题解,然后同时又对着叉姐写的两行字题解看了一个下午;

虽然基本上已经知道了这题的思路,但愣是因为自己代码实现起来太繁复,外加不确定正确性,没敢码……

但是一道题肝了一下午没肝出来,就要放弃的话,怕是太扎心了,忍不住就跑去ICPCCamp.Post问叉姐了(https://post.icpc-camp.org/d/715-2017-b-roads

看了叉姐的对于我的几个问题的回复,我总算肯定了我的思路,而且叉姐还在下面给了标程,当时可以说心情非常愉悦;

听起来是非常的exciting,但是当我看到了整篇code没用一个数组,放codeblocks里愣是编译不通过,看了半天愣是没看懂的时候,我的心情瞬间原地爆炸,整个人都是崩溃的。

当然啦,主要原因还是那天整个人状态都不是很好,心态已经属于炸了的状态,看不懂也是正常;

睡了两觉,摆好心态,再回来看,就不是很难了。

嗯,口胡结束,开始正文。

分割线

题目链接:http://202.197.224.59/OnlineJudge2/index.php/Problem/read/id/1261

Time Limit : 1000 MS Memory Limit : 65536 KB

In ICPCCamp, there are n towns conveniently labeled with 1,2,…,n and m bidirectional roads planned to be built. The i-th road will be built between cities a[i] and b[i] with cost c[i] . The builders in ICPCCamp will build the (n−1) roads with the least total cost to connect any of two cities directly or indirectly.

Bobo, the mayor of ICPCCamp is going to remove some of the roads from the construction plan. He would like to know the minimum number roads to be removed to strictly increases the total cost.

Note that the total cost is considered as +∞  if no valid (n−1)  roads exist after removing. It is also counted as "total cost strictly increases".

Input

The input contains zero or more test cases and is terminated by end-of-file. For each test case:

The first line contains two integers n and m . The i-th of the following m lines contains a[i],b[i],c[i] .

  • 2≤n≤50
  • n−1≤m≤n^2
  • 1≤a[i],b[i]≤n
  • 1≤c[i]≤10^9
  • Any two cities will be connected if all m roads are built.
  • The sum of n does not exceed 10^3 .

Output

For each case, output an integer which denotes the result.

Sample Input

3 3
1 2 1
1 3 2
2 3 3
3 4
1 2 1
1 2 1
1 3 2
1 3 3
3 4
1 2 1
1 2 1
1 3 2
1 3 2
4 6
1 2 1
1 3 1
1 4 1
2 3 1
2 4 1
3 4 1

Sample Output

1
1
2
3

题意:

在ICPCCamp里有n个城镇(即n个点,编号为1~n),然后现在计划建造m条双向道路(m条无向边,可能有重边),每条路<a,b,c>代表这条路连接了a,b两座城镇,建造这条路的花费为c;

实际的建造者会挑选m条路中的n-1条进行建造,这n-1条边满足最小生成树,即n-1条边连通所有的点并且花费总和最少;

现在Bobo想要修改建造计划,从m条计划建造的路中,去掉一些路,得到一个新的计划,使得建造者在这个新的计划下造路的时候(当然,建造者依然按照上面的原则挑路来造),花费比原来大(严格增大)。

另外要注意的是,假如新的计划中只剩下少于n-1条的路,那么就会是一个非连通图,这个时候也算作“严格增大”。

现在要求的是,最少移去几条路就可以满足“严格增大”;

题解:

  (http://files.cnblogs.com/files/dilthey/xiangtan-2017-solution.pdf)

真的是简洁明了的题解呢

详细解释一下:

分割线                                                                                                      begin

因为建造者肯定是按最小生成树来造路的;那么通过删边,让最小生成树变大的原因是什么?

必然是删掉了某条(或者某些)边之后,某两个点之间的连接被一定程度上断开,必须要通过寻找权值更大的边来把这两个边连接起来。

那么,就很容易想到是个关于“割”的问题,又因为要删去的边最少,就不难联想到有关于最小割。

就像上面的题解里说的那样,假设所有边中,有相同权值的k条边,他们连接起了一个图(连通的,或者非连通的);

我们把这个图割开(如果是非连通图,就是选取某一个连通分量割开),使得存在两个点,对于任意的weight=k的边,都不能让这两个边重新连通(直接地或间接地);

(⊙v⊙)嗯,看起来很简单的样子。

但是我们又会想到一点,怎么才能确定我们断开了某两个点的连接之后,肯定只能用权值更大的边来代替,而不能是权值更小的边来代替呢?

  考虑一种比较简单的情况,假设存在edge1=<a,b>,weight=2,但是又存在edge2=<a,b>,weight=1,那么我们如果想要删掉edge1来增大MST,这显然是不可能的。

  意思是说……?对,我们在割图的时候,edge1根本就不能被割,因为就算把它割开了,也等于没割开(听起来是如此的暴躁……

  也就是说,更一般的:

    

那么,怎样才能让最小割不把a,b割开呢?我一开始想到的是,在a,b间连一条cap=INF的边,这固然也是可以的;

当然,之前http://www.cnblogs.com/dilthey/p/7381056.html这篇文章里提到过,两个点间只有一条cap=INF的边连接的话,可以合并;

这就是https://post.icpc-camp.org/d/715-2017-b-roads/2叉姐给我的回复中提到的 “(2) 按照权值从小到大做,做完小的,把这些点缩起来。”

另外还想说的一点,是我当时想到建立一条cap=INF的边之后的顾虑,如果原本是两个连通分量,被我们这么一建边,被连到一块儿了怎么办?

由于当时不确定的东西太多,脑子一团浆糊,外加心态爆炸,想到还有这种问题的时候,吓得立马否决了自己加cap=INF边的想法了,

其实很简单的,两个连通分量,不管你用了多少条INF容量的边连到一起,最小割也不会割到这几条边上去;

那么这个时候的最小割,要么是一刀割在连通分量1上,要么是一刀割在连通分量2上;

而且,像叉姐说的:“(1) 如果某个权值的边,形成的是若干个连通块,那么要对每个连通块分别跑,答案取个小的。因为我们只要割开了某个连通块,整个图的 MST 就变大了。”;

由于如果一个图有多个连通分量,我们本来就要在每个连通分量的最小割里找个最小的;

要是它们连通了,那这个时候的求出最小割,不就省的我们去一个一个连通分量去跑了吗!?这么简单,当时居然没想到,简直了……

而如果像叉姐说那样,把两个点缩成一个点的话,那就更简单了,反正最小割也不能把一个点割开来,和不能割开cap=INF的遍一样,同样不影响。

分割线                                                                                                      end

嗯,这样思路就比较清晰了,就是:

  ①初始化令ans=INF,按权值从小到大枚举边

  ②所有weight==k的边构建起一个图(同时要做缩点操作)

  ③求得最小割(若为非连通图,则求所有连通分量的最小割,取最小的),更新ans(若mincut<ans,则ans=mincut)

  ④再次遍历所有weight==k的边,若两端点还未连通,则连通两个端点,遍历结束后,k++,回到②

这里给出叉姐给的标程(附解释):

 #include <cstdio>
#include <cstring>
#include <climits>
#include <numeric>
#include <map>
#include <queue>
#include <utility>
#include <vector> int find(std::vector<int>& parent, int u)
{
if (parent.at(u) != u) {
parent.at(u) = find(parent, parent.at(u));
}
return parent.at(u);
}//并查集 std::vector<int> bfs(const std::vector<std::vector<int>>& cap, int s)
{
int n = cap.size(); //当前图有n条边
std::vector<int> lv(n, -);//定义level[n]数组,并初始化均为-1
lv.at(s) = ;
std::queue<int> q;
q.push(s);
while (!q.empty()) {
int u = q.front();
q.pop();
for (int v = ; v < n; ++ v) {
if (cap.at(u).at(v) > && lv.at(v) == -) {
lv.at(v) = lv.at(u) + ;
q.push(v);
}
}
}
//bfs跑出层次图
return lv;//返回level[n]数组
} int dfs(const std::vector<int>& lv, std::vector<std::vector<int>>& cap, std::vector<int>& cur, int u, int t, int left)
{
if (u == t) {
return left;
}
int n = cap.size();
int delta = ;
for (auto& v = cur.at(u); v < n; ++ v) {
if (cap.at(u).at(v) > && lv.at(v) == lv.at(u) + ) {
int tmp = dfs(lv, cap, cur, v, t, std::min(left - delta, cap.at(u).at(v)));
delta += tmp;
cap.at(u).at(v) -= tmp;
cap.at(v).at(u) += tmp;
if (delta == left) {
break;
}
}
}
return delta;
} int main()
{
int n, m;
while (scanf("%d%d", &n, &m) == ) {
std::map<int, std::vector<std::pair<int, int>>> edges;
for (int i = ; i < m; ++ i) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
edges[c].emplace_back(a - , b - );
}
int result = INT_MAX;
std::vector<int> parent(n);
std::iota(parent.begin(), parent.end(), );//初始化并查集
for (auto&& iterator : edges) {//按边权遍历整个存储边的容器
auto&& es = iterator.second;//取出当前边权的所有边,定义为es
std::vector<bool> mark(n);
std::vector<std::vector<int>> cap(n, std::vector<int>(n));
for (auto&& e : es) { //遍历es中的边
auto&& a = find(parent, e.first);
auto&& b = find(parent, e.second);
if (a != b) {//如果当前边的两个端点还未连通
mark.at(a) = mark.at(b) = true;//标记这两个点的父节点
cap.at(a).at(b) ++;
cap.at(b).at(a) ++;
//加入一条边,连着两个点的父节点(相当于之前已经连通的点,都进行了缩点)
//值得注意的是,这里用了cap++,这样可以有效地解决重边的问题
}
}
for (int s = ; s < n; ++ s) {//枚举所有的点(并作为源点)
if (mark.at(s)) {//如果这个点属于当前要跑最小割的图
auto lv = bfs(cap, s);//bfs遍历图,标记当前连通块的所有点
int t = ;
while (t < s && lv.at(t) == -) {
t ++;
}//找当前连通块的点集,按升序排序的第一个点
if (t == s) { //如果这个点就是s,那代表这个连通块还未跑过,否则这个连通块就肯定已经跑过了
for (t = s + ; t < n; ++ t) {//枚举汇点
if (lv.at(t) != -) {//如果属于当前连通块
auto fl = cap;
int cut = ;
while (true) {
auto lv = bfs(fl, s);
if (lv.at(t) == -) {
break;
}
std::vector<int> cur(n);
cut += dfs(lv, fl, cur, s, t, INT_MAX);
}//求出最大流或(者说最小割)
result = std::min(result, cut);//不断更新,保证result为最小的
}
}
}
}
}
for (auto&& e : es) { //把边加入到最小生成树里
auto&& a = find(parent, e.first);
auto&& b = find(parent, e.second);
if (a != b) {
parent.at(a) = b;
}
}
//由于缩点的关系,不加cnt==n-1的判断条件也无所谓
//因为当最小生成树完整后,后面不管什么图,都会被缩成一个点,也就没有最小割了
}
printf("%d\n", result);
}
}

当然啦,标程的代码太风骚了……而且不是版本很新的编译器还编译不过去,上我自己的AC代码:

 #include<cstdio>
#include<cstring>
#include<map>
#include<vector>
#include<algorithm>
#define MAXN 53
#define MAXM MAXN*MAXN
#define INF 0x3f3f3f3f
using namespace std; int n,m;
struct Node{
int u,v,w;
};
//并查集模板开始
int par[MAXN],ran[MAXN];
void ufs_init(){for(int i=;i<=n;i++) par[i]=i,ran[i]=;}
int find(int x)
{
if(par[x] == x) return x;
else return( par[x] = find(par[x]) );
}
void unite(int x,int y)
{
x=find(x),y=find(y);
if(x == y) return;
if(ran[x] < ran[y]) par[x]=y;
else
{
par[y]=x;
if(ran[x] == ran[y]) ran[x]++;
}
}
bool isSame(int x,int y){return( find(x) == find(y) );}
//并查集模板结束
//stoer-wagner算法模板开始
int edge[MAXN][MAXN],dist[MAXN];
bool vis[MAXN],bin[MAXN];
void sw_init()
{
memset(edge,,sizeof(edge));
memset(bin,,sizeof(bin));
}
int merge(int &s,int &t)
{
memset(dist,,sizeof(dist));
memset(vis,,sizeof(vis));
int k,mincut,maxc;
for(int i=;i<=n;i++)
{
k=-, maxc=-;
for(int j=;j<=n;j++) if(!bin[j] && !vis[j] && dist[j] > maxc) {k=j;maxc=dist[j];}
if(k == -) return mincut;
vis[k]=true;
s=t; t=k;
mincut=maxc;
for(int j=;j<=n;j++) if(!bin[j] && !vis[j]) dist[j]+=edge[k][j];
}
return mincut;
}
int stoer_wagner()
{
int mincut=INF,s,t,ans;
for(int i=;i<=n-;i++)
{
ans=merge(s,t);
bin[t]=;
if(ans!= && ans<mincut) mincut=ans;
for(int j=;j<=n;j++) if(!bin[j]) edge[s][j]=(edge[j][s]+=edge[j][t]);
}
return mincut;
}
//stoer-wagner算法模板结束
bool cmp(Node a,Node b){return a.w<b.w;}
int main()
{
Node E[MAXM];
while(scanf("%d%d",&n,&m)!=EOF)
{
for(int i=;i<=m;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
E[i].u=a, E[i].v=b, E[i].w=c;
}
sort(E+,E+m+,cmp);
int ans=INF;
int edge_cnt=;
ufs_init();
for(int i=;i<=m;)
{
int now_i=i,now_w=E[i].w;
sw_init();
for(;now_w==E[i].w && i<=m;i++)//建图
{
if(!isSame(E[i].u,E[i].v))
{
edge[find(E[i].u)][find(E[i].v)]++;
edge[find(E[i].v)][find(E[i].u)]++;
}
} ans=min(ans,stoer_wagner()); for(int i=now_i;now_w==E[i].w && i<=m;i++)//逐步建立最小生成树
{
if(!isSame(E[i].u,E[i].v))
{
unite(E[i].u,E[i].v);
edge_cnt++;
}
}
if(edge_cnt>=n-) break;
}
printf("%d\n",ans);
}
}

PS.可以看到,stoer-wagner算法稍微做一点点修改,就可以直接用来求多个连通分量最小割的最小者,非常方便,而且相比于标程的枚举源汇点求最大流的方法要快。

PS2.当然,标程中直接用并查集的性质来做缩点,展现出了对于并查集较深的理解,对于我这种只能用来做做模板题的咸鱼来说,是非常值得学习的。

(感觉这道题A了还是要截图纪念一下的

XTU 1261 - Roads - [最小割][2017湘潭邀请赛B题(江苏省赛)]的更多相关文章

  1. XTU 1264 - Partial Sum - [2017湘潭邀请赛E题(江苏省赛)]

    2017江苏省赛的E题,当时在场上看错了题目没做出来,现在补一下…… 题目链接:http://202.197.224.59/OnlineJudge2/index.php/Problem/read/id ...

  2. XTU 1267 - Highway - [树的直径][2017湘潭邀请赛H题(江苏省赛)]

    这道题可能有毒……总之一会儿能过一会儿不能过的,搞的我很心烦…… 依然是上次2017江苏省赛的题目,之前期末考试结束了之后有想补一下这道题,当时比较懵逼不知道怎么做……看了题解也不是很懂……就只好放弃 ...

  3. XTU 1260 - Determinant - [2017湘潭邀请赛A题(江苏省赛)][高斯消元法][快速幂和逆元]

    是2017江苏省赛的第一题,当时在场上没做出来(废话,那个时候又不懂高斯消元怎么写……而且数论也学得一塌糊涂,现在回来补了) 省赛结束之后,题解pdf就出来了,一看题解,嗯……加一行再求逆矩阵从而得到 ...

  4. 【最小割】【网络流24题】【P2762】 太空飞行计划问题

    Description W 教授正在为国家航天中心计划一系列的太空飞行.每次太空飞行可进行一系列商业性实验而获取利润.现已确定了一个可供选择的实验集合E={E1,E2,-,Em},和进行这些实验需要使 ...

  5. 2017 湘潭邀请赛&JSCPC G&J

    训练的时候对G想了一个假算法..也有很大可能是写错了.. 下来一看别人的G 看起来很奇妙.. 开始把所有的左括号翻成右括号,然后cost*=-1 这样在优先队列中就是最优的 然后for每一段 如果前缀 ...

  6. 2018湘潭邀请赛C题(主席树+二分)

    题目地址:https://www.icpc.camp/contests/6CP5W4knRaIRgU 比赛的时候知道这题是用主席树+二分,可是当时没有学主席树,就连有模板都不敢套,因为代码实在是太长了 ...

  7. 2017湘潭大学邀请赛G题(贪心+优先队列)

    参考博客:http://www.cnblogs.com/chendl111/p/6891770.html 题目链接:https://www.icpc.camp/contests/4mYguiUR8k0 ...

  8. 2017湘潭大学邀请赛E题(贪心)

    链接:https://www.icpc.camp/contests/4mYguiUR8k0GKE Partial Sum Input The input contains zero or more t ...

  9. 2017湘潭大学邀请赛H题(树的直径)

    链接:https://www.icpc.camp/contests/4mYguiUR8k0GKE H. Highway The input contains zero or more test cas ...

随机推荐

  1. ios开发之--开发中可能会用到的一些函数

    rand() ----随机数 abs() / labs() ----整数绝对值 fabs() / fabsf() / fabsl() ----浮点数绝对值 floor() / floorf() / f ...

  2. Netty权威指南之Netty入门程序

    package com.hjp.netty.netty; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Chan ...

  3. 【NodeJS】http-server.cmd

    npm install http-server @echo off  start cmd /k "D:\Program Files\nodejs\node_global\http-serve ...

  4. 数据库iops的理解

    想购买阿里云的RDS mysql,想请教下最大连接数是请求数吗?如下图,600最大可支持连接数,那一个页面查询30次,20个人同时请求,数据库不就超载了么?(内存2400MB,专用数据服务器,只能支持 ...

  5. EventHandler中如何提升用户权限(模拟管理员权限)

    不论是在工作流或是EventHandler中,我们经常希望模拟管理员权限,从而可以通过程序自定义列表项的权限. 在工作流中可以用如下代码来提升权限:(以下代码实现的功能是断开列表项所继承的权限,除管理 ...

  6. mongo数据库查询结果不包括_id字段方法

    db.GPRS_PRODUCT_HIS_FEE.find({"条件字段" : "412171211145135"},{_id:0}) db.GPRS_PRODU ...

  7. java.util.concurrent.RejectedExecutionException 线程池饱和

    java.util.concurrent.RejectedExecutionException at java.util.concurrent.ThreadPoolExecutor$AbortPoli ...

  8. Unity使用OpenGL绘制线段

    using System.Collections; using System.Collections.Generic; using UnityEngine; public class ShowGrid ...

  9. 深入浅出MFC——Document-View深入探讨(五)

    1. MFC之所以为Application Framework,最重要的一个特征就是它能够将管理数据的程序代码和负责数据显示的程序代码分离开来,这种能力由MFC的Document/View提供.Doc ...

  10. 【变态问题】在发现“XXXX”类型前实体框架已使用默认 DbConfiguration 实例。

    今天在调试MVC反射调用EF写的dll 一直报错如下: 在发现“VipHallDbConfiguration”类型前实体框架已使用默认 DbConfiguration 实例.“VipHallDbCon ...