这是一道很好的套路题。助你掌握新套路。
第一问太愚蠢了,求最小割,学过网络流应该都会。
重点是第二问,求最小割割的最少边数。太难了,默默打开题解, g e t get get到新套路。
建图时,我们将边的权值乘以 ( ( (( ((一个较大的数 a ) + 1 ) a)+1) a)+1),设新的图的最小割为 n e w a n s newans newans,那么原图最小割为 n e w a n s / a newans/a newans/a,最小割割的最少边数为 n e w a n s % a newans\%a newans%a。
原理(摘自题解):因为最小割的边集 E = { w 1 , w 2 . . . w n } E=\{w_1,w_2...w_n\} E={w1,w2...wn}中有
w 1 + w 2 + w 3 … + w n = a n s w_1+w_2+w_3…+w_n=ans w1+w2+w3…+wn=ans
其中 a n s ans ans为本来的最小割。
所以必然有:
w 1 × a + w 2 × a + w 3 × a … + w n × a = a n s × a w_1\times a+w_2\times a+w_3\times a…+w_n\times a=ans\times a w1×a+w2×a+w3×a…+wn×a=ans×a
于是又有:
w 1 × a + 1 + w 2 × a + 1 + w 3 × a + 1 … + w n × a + 1 = a n s × a + k w1\times a+1+w2\times a+1+w3\times a+1…+wn\times a+1=ans\times a+k w1×a+1+w2×a+1+w3×a+1…+wn×a+1=ans×a+k
k k k为最小割的边数, k < = m < = 1000 k<=m<=1000 k<=m<=1000
最后得到结论:原图最小割为 n e w a n s / a newans/a newans/a,最小割割的最少边数为 n e w a n s % a newans\%a newans%a。
于是这题就解决了:
#include
#define ts cout<<"ok"<
#define lowbit(x) (x)&(-x)
#define oo (1e18)
#define int long long
#define LL unsigned long long
#define hh puts("")
using namespace std;
int n,m,cnt=1,tot,ans,st,ed,head[10005],cur[10005],d[10005],base=2003;
struct Edge{
int v,nx,s;
}e[2000005];
inline int read(){
int ret=0,ff=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') ff=-ff;ch=getchar();}
while(isdigit(ch)){ret=(ret<<3)+(ret<<1)+(ch^48);ch=getchar();}
return ret*ff;
}
inline void add(int x,int y,int z){
e[++cnt].v=y;
e[cnt].s=z;
e[cnt].nx=head[x];
head[x]=cnt;
}
inline bool bfs(){
for(int i=0;i<=ed;i++) d[i]=0,cur[i]=head[i];
d[st]=1;
queue<int> q;
q.push(st);
while(!q.empty()){
int now=q.front();
q.pop();
for(int i=head[now];i;i=e[i].nx){
int v=e[i].v;
if(e[i].s&&!d[v]){
d[v]=d[now]+1;
if(v==ed) return 1;
q.push(v);
}
}
}
return 0;
}
int dfs(int now,int ma){
if(now==ed){
ans+=ma;
return ma;
}
int used=0,t;
for(int i=cur[now];i;i=e[i].nx){
cur[now]=i;
int v=e[i].v;
if(e[i].s&&d[v]==d[now]+1){
if(t=dfs(v,min(e[i].s,ma-used))){
used+=t;
e[i].s-=t;
e[i^1].s+=t;
if(used==ma) break;
}
}
}
return used;
}
signed main(){
n=read(),m=read();
st=1,ed=n;
for(int i=1;i<=m;i++){
int x=read(),y=read(),z=read();
add(x,y,z*base+1),add(y,x,0);
}
while(bfs()) dfs(st,1e16);
printf("%lld %lld",ans/base,ans%base);
return 0;
}
拓展一下,也就是原题 ( ( (这题简化过 ) ) ):我们求出了边的数量,但如果要求这些边的编号,又应该怎么做?
参考了这篇博客:
首先,网络流的核心是从起点通过多条路径到终点,那么,我们可以想出,这每一条合格 ( ( (可以从起点流向终点 ) ) )的路径,一定会有其中一条或多条边满流了,那么就代表这条边是这条路径的核心,去掉这条边,整张图的完整流量就会减去这条边的流量,我们称这些边为贡献边。
那么,在最小割所求的最小容量中所割掉的边的集合中,一定全部是贡献边。显然。
那么,怎么求这些边呢 ( ( (编号按字典序排并且要最少的边 ) ) )?
由于这些贡献边控制了流量,所以,我们把它删除掉,再流一遍,会得到一个新的 a n s ( a n s ans(ans ans(ans可能是0 ) ) ), a n s + ans+ ans+这条边的权值 = = =不删这条边的最大流,依靠这条等式,就可以去判断了,最后排序即可。