• 网络流


    一个网络 (G=(V,E)) 是一张有向图,图中每条有向边 ((x,y)in E) 都有一个给定的权值 (c(x,y)),称为边的容量。特别地,若 ((x,y) otin E),则 (c(x,y)=0)。图中还有两个指定的特殊节点 (S,T in V(S eq T)) 分别被称为源点和汇点

    (f(x,y)) 是定义在节点二元组 ((x in V,y in V)) 上的实数函数,且满足:

    容量限制:(f(x,y) leq c(x,y))

    斜对称:(f(x,y)=-f(y,x))

    流量守恒:(forall x eq S, x eq T, sum_{(u,x) in E} f(u,x) = sum_{(x,v) in E}f(x,v))

    (f) 称为网络的流函数,对于 ((x,y) in E)(f(x,y)) 称为边的流量,(c(x,y)-f(x,y)) 称为边的剩余流量

    (sum_{(S,v) in E} f(S,v)) 称为整个网络的流量((S) 为源点)

    最大流

    Edmond—Karp算法

    若一条从源点 (S) 到汇点 (T) 的路径上各条边的剩余容量都大于 (0),则称这条路径为一条增广路

    (EK) 算法为用 (bfs) 不断寻找增广路,直到网络上不存在增广路为止

    (bfs) 找到任意一条从 (S)(T) 的路径,记录路径上各边的剩余容量的最小值,则网络的流量就可以增加这个最小值

    利用邻接表成对存储来实现 ((x,y)) 剩余容量的减小,((y,x)) 剩余容量的增大

    时间复杂度上界为 (O(nm^2)),一般可以处理 (1e3 sim 1e4) 规模的网格

    (code)

    bool bfs()
    {
    	memset(vis,0,sizeof(vis));
    	queue<int> q;
    	q.push(s);
    	vis[s]=true;
    	res[s]=inf;
    	while(!q.empty())
    	{
    		int x=q.front();
    		q.pop();
    		for(int i=head[x];i;i=e[i].nxt)
    		{
    			int y=e[i].to,v=e[i].v;
    			if(vis[y]||!v) continue;
    			res[y]=min(res[x],v);
    			pre[y]=i;
    			q.push(y);
    			vis[y]=true;
    		}
    	}
    	return vis[t];
    }
    void update()
    {
    	int x=t;
    	while(x!=s)
    	{
    		int i=pre[x];
    		e[i].v-=res[t];
    		e[i^1].v+=res[t];
    		x=e[i^1].to;
    	}
    	ans+=res[t];
    }
    
    ......
    
    while(bfs()) update();
    

    Dinic算法

    在任意时刻,网络中所有节点以及剩余容量大于(0)的边构成的子图称为残量网络

    (Dinic)算法引入分层图的概念,(d_x) 表示从 (S)(x) 最少需要经过的边数,为了方便处理设 (d_S=1),分层图为残量网络中满足 (d_y =d_x +1) 的边 ((x,y)) 构成的子图

    时间复杂度上界为 (O(n^2m)),一般可以处理 (1e4 sim 1e5) 规模的网格,求解二分图最大匹配的时间复杂度为 (O( sqrt nm))

    (Dinic) 算法中还可以加入若干剪枝来优化

    (res),表示当前节点的流量剩余,若 (res leqslant 0),停止寻找增广路

    (cur_x),表示当到达到(x)节点时,直接从 (cur_x) 对应的边开始遍历,实际表示上一次从 (x) 遍历到了哪一条边,因为在这之间的边都已经被彻底增广过了,所以可以直接跳转,称为当前弧优化

    (code)

    bool bfs()
    {
        queue<int> q;
        for(int i=s;i<=t;++i) d[i]=0,cur[i]=head[i];
        d[s]=1,q.push(s);
        while(!q.empty())
        {
            int x=q.front();
            q.pop();
            for(int i=head[x];i;i=e[i].nxt)
            {
                int y=e[i].to;
                if(d[y]||!e[i].v) continue;
                d[y]=d[x]+1,q.push(y);
            }
        }
        return d[t];
    }
    int dfs(int x,int lim)
    {
        if(x==t) return lim;
        int res=lim,flow;
        for(int &i=cur[x];i;i=e[i].nxt)
        {
            int y=e[i].to,v=e[i].v;
            if(d[y]!=d[x]+1||!v) continue;
            if(flow=dfs(y,min(v,res)))
            {
                res-=flow;
                e[i].v-=flow;
                e[i^1].v+=flow;
                if(!res) break;
            }
        }
        return lim-res;
    }
    int dinic()
    {
        int flow,ans=0;
        while(bfs())
            while(flow=dfs(s,inf))
                ans+=flow;
        return ans;
    }
    

    若要求每条边的所用的流量,可以将原图备份,跑完最大流后,用原图的容量减去当前的剩余容量即可求得所用流量

    对于容量为实数,应该这样写:

    (code:)

    bool bfs()
    {
    	queue<int> q;
    	for(int i=s;i<=t;++i) d[i]=0,cur[i]=head[i];
    	q.push(s),d[s]=1;
    	while(!q.empty())
    	{
    		int x=q.front();
    		q.pop();
    		for(int i=head[x];i;i=e[i].nxt)
    		{
    			int y=e[i].to;
                double v=e[i].v;
    			if(d[y]||fabs(v)<eps) continue;
    			d[y]=d[x]+1,q.push(y);
    		}
    	}
    	return d[t];
    }
    double dfs(int x,double lim)
    {
    	if(x==t) return lim;
    	double res=lim,flow,sum=0;
    	for(int &i=cur[x];i;i=e[i].nxt)
    	{
    		int y=e[i].to;
            double v=e[i].v;
    		if(d[y]!=d[x]+1||fabs(v)<eps) continue;
    		flow=dfs(y,min(res,v)),sum+=flow;
    		res-=flow,e[i].v-=flow,e[i^1].v+=flow;
    		if(fabs(res)<eps) break;
    	}
    	return sum;
    }
    

    不能像之前一样用流量限制减去剩余流量,因为限制可能为一个极大值,其减去一个较小值后,会舍掉精度

    二分图最大匹配的必须边和可行边

    跑完网络流后在残量网络上进行 (Tarjan) 缩点

    必须边:((x,y)) 流量为 (1),且 (x)(y) 在残量网络上属于不同的强连通分量

    可行边:((x,y)) 流量为 (1),或 (x)(y) 在残量网络上属于同一个强连通分量

    最小割

    图中所有的割中,边权值和最小的割为最小割,最大流 (=) 最小割

    利用最小割,将求解最大收益转化为最小代价

    平面图最小割 (=) 对偶图最短路,应用有狼抓兔子海拔

    最小割的必须边和可行边

    可行边:被某一种最小割的方案包含

    判断:满流,在残量网络上不存在 (x)(y) 的路径。(缩点后判断,等价于 (x,y) 属于不同的强连通分量)

    证明:存在路径,说明其该边不存在必要性

    必须边:一定在最小割中、扩大容量后能增大最大流

    判断:满流,是可行边,在残量网络上存在 (S)(x)(y)(T) 的路径。(缩点后判断,等价于 (x)(S) 属于同一个强连通分量,(y)(T) 属于同一个强连通分量)

    证明:不割的话,(S)(T) 就会连通

    最大权闭合子图

    若有向图 (G) 的子图 (V) 满足:(V) 中顶点的所有出边均指向 (V) 内部的顶点,则称 (V)(G) 的一个闭合子图

    (G) 中的点有点权,则点权和最大的闭合子图称为有向图 (G) 的最大权闭合子图

    建立源点 (S) 和汇点 (T) ,源点 (S) 连所有点权为正的点,容量为该点点权;其余点连汇点 (T),容量为该点点权的相反数,对于原图中的边 ((x,y)),连边 ((x,y,inf)),割从源点出发的边表示不选这个点,割指向汇点的边表示选这个点

    最大权闭合图的点权和 (=) 所有正权点权值和 (-) 最小割

    也就是最大收益转化为了最小代价

    在残量网络中由源点 (S) 能够访问到的点,就构成一个点数最少的最大权闭合图

    最大密度子图

    一个无向图 (G=(V,E)) 的边数 (|E|) 与点数 (|V|) 的比值 (D=frac{|E|}{|V|}) 称为它的密度

    (G) 的一个子图 (G^prime=(V^prime,E^prime)),使得 (D^prime=frac{|E^prime|}{|V^prime|}) 最大

    二分 (gleqslantfrac{|E|}{|V|}),得 (|E|-|V|×g geqslant0)

    源点 (S) 向所有边连容量为 (1) 的边,边向其两端的点连容量为 (inf) 的边,点向汇点 (T) 连容量为 (g) 的边

    二分下界:(frac{1}{n}),上界:(m),精度:(frac{1}{n^2})

    (code:)

    bool bfs()
    {
        for(int i=s;i<=t;++i) cur[i]=head[i];
        memset(d,0,sizeof(d));
        queue<int> q;
        q.push(s);
        d[s]=1;
        while(!q.empty())
        {
            int x=q.front();
            q.pop();
            for(int i=head[x];i;i=e[i].nxt)
            {
                int y=e[i].to;
                double v=e[i].v;
                if(d[y]||fabs(v)<eps) continue;
                d[y]=d[x]+1;
                q.push(y);
            }
        }
        return d[t];
    }
    double dfs(int x,double lim)
    {
        if(x==t) return lim;
        double res=lim,flow;
        for(int &i=cur[x];i;i=e[i].nxt)
        {
            int y=e[i].to;
            double v=e[i].v;
            if(d[y]!=d[x]+1||fabs(v)<eps) continue;
            if(fabs(flow=dfs(y,min(res,v)))>=eps)
            {
                res-=flow;
                e[i].v-=flow;
                e[i^1].v+=flow;
                if(fabs(res)<eps) break;
            }
        }
        return lim-res;
    }
    double dinic()
    {
        double flow,ans=0;
        while(bfs())
            while(fabs(flow=dfs(s,inf))>=eps)
                ans+=flow;
        return ans;
    }
    double check(double x)
    {
        edge_cnt=1;
        memset(head,0,sizeof(head));
        for(int i=1;i<=n;++i) add(i+m,t,x);
        for(int i=1;i<=m;++i)
        {
            int x=ed[i].x,y=ed[i].y;
            add(s,i,1.0),add(i,x+m,inf),add(i,y+m,inf);
        }
        return m*1.0-dinic();
    }
    int work()
    {
        int ans=0;
        memset(du,0,sizeof(du));
        memset(vis,0,sizeof(vis));
        check(g);
        for(int i=1;i<=m;++i)
        {
            int x=ed[i].x,y=ed[i].y;
            if(d[i])
            {
                if(++du[x]==1) ans++,vis[x]=true;
                if(++du[y]==1) ans++,vis[y]=true;
            }
        }
        return ans;   
    }
    
    ......
    
    l=0,r=m,g=0;
    while(l+1/((double)n*(double)n)<r)
    {
        double mid=(l+r)/2.0;
        if(check(mid)>eps) g=l=mid;
        else r=mid;
    }
    printf("%d
    ",work());
    for(int i=1;i<=n;++i)
        if(vis[i])
            printf("%d
    ",i);
    

    最小割二元关系

    二元关系指如选和不选的关系

    建立最小割模型,来解决一系列问题,如happiness文理分科人员雇佣

    费用流

    Edmond—Karp算法

    (code)

    bool spfa()
    {
    	for(int i=1;i<=n;++i) dis[i]=inf;
    	memset(vis,0,sizeof(vis));
    	queue<int> q;
    	q.push(s);
    	vis[s]=true;
    	dis[s]=0;
    	res[s]=inf;
    	while(!q.empty())
    	{
    		int x=q.front();
    		q.pop();
    		vis[x]=false;
    		for(int i=head[x];i;i=e[i].nxt)
    		{
    			int y=e[i].to,v=e[i].v,c=e[i].c;
    			if(dis[y]>dis[x]+c&&v)
    			{
    				dis[y]=dis[x]+c;
    				res[y]=min(res[x],v);
    				pre[y]=i;
    				if(!vis[y])
                    {
                        vis[y]=true;
                        q.push(y);
                    }
    			}
    		}
    	}
    	return dis[t]!=inf;
    }
    void update()
    {
    	int x=t;
    	while(x!=s)
    	{
    		int i=pre[x];
    		e[i].v-=res[t];
    		e[i^1].v+=res[t];
    		x=e[i^1].to;
    	}
    	ans+=res[t];
    	sum+=res[t]*dis[t];
    }
    
    ......
    
    while(spfa()) update();
    

    Dinic算法

    (code)

    bool spfa()
    {
        queue<int> q;
        for(int i=1;i<=n;++i) dis[i]=inf,vis[i]=false;
        q.push(s),vis[s]=true,dis[s]=0;
        while(!q.empty())
        {
            int x=q.front();
            q.pop(),vis[x]=false;
            for(int i=head[x];i;i=e[i].nxt)
            {
                int y=e[i].to,c=e[i].c;
                if(dis[y]<=dis[x]+c||!e[i].v) continue;
                dis[y]=dis[x]+c;
                if(!vis[y]) q.push(y),vis[y]=true;
            }
        }
        return dis[t]!=inf;
    }
    int dfs(int x,int lim)
    {
        if(x==t) return lim;
        vis[x]=true;
        int flow,res=lim;
        for(int i=head[x];i;i=e[i].nxt)
        {
            int y=e[i].to,v=e[i].v;
            if(dis[y]!=dis[x]+e[i].c||!v||vis[y]) continue;
            if(flow=dfs(y,min(res,v)))
            {
                res-=flow;
                e[i].v-=flow;
                e[i^1].v+=flow;
                if(!res) break;
            }
        }
        return lim-res;
    }
    void dinic()
    {
        int flow;
        while(spfa())
            while(flow=dfs(s,inf))
                ans+=flow,sum+=flow*dis[t];
    }
    

    可以去求解二分图带权匹配

    有上下界限制的网络流

    无源汇有上下界可行流

    (n)个点,(m)条边的网络,求一个可行解,使得边 ((x,y)) 的流量介于 ([ low_{x,y},up_{x,y} ]) 之间,并且整个网络满足流量守恒

    (up_{x,y}-low_{x,y}) 作为容量上界,(0) 作为容量下界

    (in_x=sumlimits_{i o x} low(i,x)-sumlimits_{x o i} low(x,i))

    (in_x >0),则从源点 (S)(x) 连边,容量为 (in_x),反之,则从 (x) 向汇点 (T) 连边,容量为 (-in_x)

    在该网络上求最大流,求完后每条边的流量再加上容量下界即为一种可行流

    (code:)

    bool bfs()
    {
    	memcpy(cur,head,sizeof(head));
    	memset(d,0,sizeof(d));
    	queue<int> q;
    	q.push(s);
    	d[s]=1;
    	while(!q.empty())
    	{
    		int x=q.front();
    		q.pop();
    		for(int i=head[x];i;i=e[i].nxt)
    		{
    			int y=e[i].to,v=e[i].v;
    			if(d[y]||!v) continue;
    			d[y]=d[x]+1;
    			q.push(y);
    		}
    	}
    	return d[t];
    }
    int dfs(int x,int lim)
    {
    	if(x==t) return lim;
    	int res=lim,k;
    	for(int &i=cur[x];i;i=e[i].nxt)
    	{
    		int y=e[i].to,v=e[i].v;
    		if(d[y]!=d[x]+1||!v) continue;
    		if(k=dfs(y,min(res,v)))
    		{
    			res-=k;
    			e[i].v-=k;
    			e[i^1].v+=k;
    			if(!res) break;
    		}
    	}
    	return lim-res;
    }
    int dinic()
    {
        int flow,ans=0;
        while(bfs())
            while(flow=dfs(s,inf))
                ans+=flow;
        return ans;
    }
    bool check()
    {
        for(int i=head[s];i;i=e[i].nxt)
            if(e[i].v)
                return false;
        return true;
    }
    
    ......
    
    for(int i=1;i<=m;++i)
    {
        int a,b,up;
        read(a),read(b),read(low[i]),read(up);
        in[a]-=low[i],in[b]+=low[i];
        add(a,b,up-low[i]);
    }
    for(int i=1;i<=n;i++)
    {   
        if(in[i]>0) add(s,i,in[i]);
        else add(i,t,-in[i]);
    }
    dinic();
    if(check()) 
    {
        puts("YES");
        for(int i=1;i<=m;i++) printf("%d
    ",e[(i<<1)^1].v+low[i]);
    }
    else puts("NO");
    

    有源汇有上下界最大流

    (T)(S) 连一条容量上界为 (inf),容量下界为 (0) 的边,使有源汇转化为无源汇

    在残量网络上再求原源点到原汇点的最大流

    (code:)

    bool bfs()
    {
    	memcpy(cur,head,sizeof(head));
    	memset(d,0,sizeof(d));
    	queue<int> q;
    	q.push(s);
    	d[s]=1;
    	while(!q.empty())
    	{
    		int x=q.front();
    		q.pop();
    		for(int i=head[x];i;i=e[i].nxt)
    		{
    			int y=e[i].to,v=e[i].v;
    			if(d[y]||!v) continue;
    			d[y]=d[x]+1;
    			q.push(y);
    		}
    	}
    	return d[t];
    }
    int dfs(int x,int lim)
    {
    	if(x==t) return lim;
    	int res=lim,k;
    	for(int &i=cur[x];i;i=e[i].nxt)
    	{
    		int y=e[i].to,v=e[i].v;
    		if(d[y]!=d[x]+1||!v) continue;
    		if(k=dfs(y,min(res,v)))
    		{
    			res-=k;
    			e[i].v-=k;
    			e[i^1].v+=k;
    			if(!res) break;
    		}
    	}
    	return lim-res;
    }
    int dinic()
    {
        int flow,ans=0;
        while(bfs())
            while(flow=dfs(s,inf))
                ans+=flow;
        return ans;
    }
    bool check()
    {
        for(int i=head[s];i;i=e[i].nxt)
            if(e[i].v)
                return false;
        return true;
    }
    
    ......
    
    for(int i=1;i<=m;++i)
    {
    	int a,b,up,low;
    	read(a),read(b),read(low),read(up);
    	in[a]-=low,in[b]+=low;
    	add(a,b,up-low);
    }
    for(int i=1;i<=n;i++)
    {   
        if(in[i]>0) add(s,i,in[i]);
        else add(i,t,-in[i]);
    }
    add(T,S,inf);
    dinic();
    ans=e[edge_cnt].v;
    e[edge_cnt].v=e[edge_cnt^1].v=0;
    if(check()) 
    {
        s=S,t=T;
        printf("%d",ans+dinic());
    }
    else puts("NO");
    

    有源汇有上下界最小流

    先不添加 (T)(S) 的边,求一次超级源到超级汇的最大流。

    然后再添加一条从 (T)(S) 下界为 (0) ,上界为 (inf) 的边,在残量网络上再求一次超级源到超级汇的最大流

    流经 (T)(S) 的边的流量就是最小流的值

    (code:)

    bool bfs()
    {
    	memcpy(cur,head,sizeof(head));
    	memset(d,0,sizeof(d));
    	queue<int> q;
    	q.push(s);
    	d[s]=1;
    	while(!q.empty())
    	{
    		int x=q.front();
    		q.pop();
    		for(int i=head[x];i;i=e[i].nxt)
    		{
    			int y=e[i].to,v=e[i].v;
    			if(d[y]||!v) continue;
    			q.push(y);
    			d[y]=d[x]+1;
    		}
    	}
    	return d[t];
    }
    int dfs(int x,int lim)
    {
    	if(x==t) return lim;
    	int res=lim,k;
    	for(int &i=cur[x];i;i=e[i].nxt)
    	{
    		int y=e[i].to,v=e[i].v;
    		if(d[y]!=d[x]+1||!v) continue;
    		if(k=dfs(y,min(res,v)))
    		{
    			res-=k;
    			e[i].v-=k;
    			e[i^1].v+=k;
    			if(!res) break;
    		}
    	}
    	return lim-res;
    }
    int dinic()
    {
    	int k,flow=0;
    	while(bfs())
    	{
    		while(k=dfs(s,inf))
    		{
    			flow+=k; 
    		}
    	}
    	return flow;
    }
    bool check()
    {
        for(int i=head[s];i;i=e[i].nxt)
            if(e[i].v)
                return false;
        return true;
    }
    
    ......
    
    for(int i=1;i<=m;++i)
    {
    	int a,b,up,low;
    	read(a),read(b),read(low),read(up);
    	in[a]-=low,in[b]+=low;
    	add(a,b,up-low);
    }
    for(int i=1;i<=n;i++)
    {   
        if(in[i]>0) add(s,i,in[i]);
        else add(i,t,-in[i]);
    }
    dinic();
    add(T,S,inf);
    dinic();
    if(!check())
    {
        puts("please go home to sleep");
        return 0;
    }
    printf("%d",e[edge_cnt].v);
    

    循环流

    以最大费用循环流为例,对于边 ((x,y,v,c)),若费用为正,则将其先流满,记录费用总和 (sum),通过建立源汇点来实现补流,边正常连。若费用为负,则连边 ((x,y,v,-c))

    然后跑最小费用最大流得出费用 (ans),最终最大费用循环流求解的答案为 (sum-ans)

    JZOJ Tree

    最大费用循环流,树上的边从上向下连,容量为 (d),费用为 (0),每条路径的边从下向上连,容量为 (1),费用为 (c)

    求解时,先将所有边跑满流,然后增加源汇点来使流量平衡,通过跑最小费用最大流来实现退流,删去不合法的边的贡献

    还有一种更强的做法:

    转载自JZOJ 100003. 【NOI2017模拟.4.1】 Tree(费用流)

    (code:)

    #include<bits/stdc++.h>
    #define maxn 500010
    #define maxm 5000010
    #define inf 1000000000000000
    using namespace std;
    typedef long long ll;
    template<typename T> inline void read(T &x)
    {
        x=0;char c=getchar();bool flag=false;
        while(!isdigit(c)){if(c=='-')flag=true;c=getchar();}
        while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
        if(flag)x=-x;
    }
    int T,n,m,s,t;
    ll ans;
    int in[maxn],de[maxn];
    ll dis[maxn];
    bool vis[maxn];
    struct edge
    {
        int to,nxt,v;
        ll c;
    }e[maxm];
    int head[maxn],edge_cnt;
    void add(int from,int to,int val,int cost)
    {
        e[++edge_cnt]=(edge){to,head[from],val,cost};
    	head[from]=edge_cnt;
        e[++edge_cnt]=(edge){from,head[to],0,-cost};
    	head[to]=edge_cnt;
    }
    void Add(int from,int to,int val,ll cost)
    {
        in[from]+=val,in[to]-=val,ans+=cost,add(from,to,val,cost);
    }
    struct Edge
    {
        int to,nxt,v;
    }ed[maxn];
    int hd[maxn],e_cnt;
    void link(int from,int to,int val)
    {
        ed[++e_cnt]=(Edge){to,hd[from],val};
        hd[from]=e_cnt;
    }
    void dfs_pre(int x,int fa)
    {
    	de[x]=de[fa]+1;
    	for(int i=hd[x];i;i=ed[i].nxt)
    	{
    		int y=ed[i].to;
    		if(y==fa) continue;
            Add(x,y,ed[i].v,0),dfs_pre(y,x);
    	}	
    }
    bool spfa()
    {
        for(int i=s;i<=t;++i) vis[i]=0,dis[i]=inf;
        queue<int> q;
        q.push(s),dis[s]=0,vis[s]=true;
        while(!q.empty())
        {
            int x=q.front();
            q.pop(),vis[x]=false;
            for(int i=head[x];i;i=e[i].nxt)
            {
                int y=e[i].to;
                ll v=e[i].v,c=e[i].c;
                if(dis[y]>dis[x]+c&&v)
                {
                    dis[y]=dis[x]+c;
                    if(!vis[y])
                    {
                        vis[y]=true;
                        q.push(y);
                    }
                }
            }
        }
        return dis[t]!=inf;
    }
    ll dfs(int x,ll lim)
    {
        if(x==t) return lim;
        vis[x]=true;
        ll res=lim,flow;
        for(int i=head[x];i;i=e[i].nxt)
        {
            int y=e[i].to;
            ll v=e[i].v,c=e[i].c;
            if(dis[y]!=dis[x]+c||!v||vis[y]) continue;
            if(flow=dfs(y,min(res,v)))
            {
                res-=flow;
                e[i].v-=flow;
                e[i^1].v+=flow;
                if(!res) break;
            }
        }
        return lim-res;
    }
    ll dinic()
    {
        ll flow,sum=0;
        while(spfa())
            while(flow=dfs(s,inf))
                sum+=flow*dis[t];
        return sum;
    }
    void clear()
    {
        e_cnt=ans=0,edge_cnt=1;
        memset(in,0,sizeof(in));
        memset(hd,0,sizeof(hd));
        memset(head,0,sizeof(head));
    }
    int main()
    {
        read(T);
        while(T--)
        {
            clear(),read(n),read(m),t=n+1;
            for(int i=1;i<n;++i)
            {
                int x,y,v;
                read(x),read(y),read(v);
                link(x,y,v),link(y,x,v);
            }
            dfs_pre(1,0);
            for(int i=1;i<=m;++i)
            {
                int x,y,v;
                read(x),read(y),read(v);
                if(de[x]<de[y]) swap(x,y);
                Add(x,y,1,v);
            }
            for(int i=1;i<=n;++i)
            {
                if(in[i]>0) add(s,i,in[i],0);
                else add(i,t,-in[i],0);
            }
            printf("%lld
    ",ans-dinic());
        }
        return 0;
    }
    
  • 相关阅读:
    比服务器的问题
    android studio adb.exe已停止工作(全面成功版 进程的查询和开启)
    安卓学习123
    java eclipse 安卓环境配置
    host访问goole
    Android studio自带的sample例子
    马士兵2string buffuer
    tomcat内存溢出,设置
    spring mvc 学习笔记【1】---前言
    dbutils报错:com.microsoft.sqlserver.jdbc.SQLServerException: 无法识别元数据的表
  • 原文地址:https://www.cnblogs.com/lhm-/p/12229508.html
Copyright © 2020-2023  润新知