连刷两周的网络流,对建模也有一定的认识。做此小结。
最大流
最大流可以说是网络流算法中比较简单的了,基本都是靠一条流来表示一种实际方案,常用于求方案数,最大值,可行性等。
POJ 3281 Dining
题目大意:有一些牛,一些饮料和一些食品。饮料和食品都只有一种。每个牛有自己喜欢的食品和饮料。问能否合理分配使得每个牛的食品和饮料都是自己喜欢的。
建模思路:平常的网络流的模型是源点连一种物品,汇点连另一种物品做匹配,然而这个题一群牛对应着两种物品,该怎么办呢?记起一条s-t流可以表示一种方案,我们将饮料和源点相连,食物和汇点相连,牛放在中间和他的喜好相连直接跑最大流,若最大流等于牛的个数,就存在这么一种方案。另外要对牛拆点,避免一个牛匹配了两种饮料的问题。
代码
#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstdlib>
#include<map>
#include<vector>
#include<ctime>
#include<stack>
#define mp make_pair
#define pa pair<int,int>
#define INF 0x3f3f3f3f
#define inf 0x3f
#define fi first
#define se second
#define pb push_back
#define ll long long
#define ull unsigned long long
using namespace std;
inline ll read()
{
long long f=1,sum=0;
char c=getchar();
while (c<'0' || c>'9')
{
if (c=='-') f=-1;
c=getchar();
}
while (c>='0' && c<='9')
{
sum=sum*10+c-'0';
c=getchar();
}
return sum*f;
}
const int MAXN=510;
const int MAXM=100010;
struct edge
{
int next,to,val;
};
edge e[MAXM];
int head[MAXN],cnt=1;
void addedge(int u,int v,int w)
{
e[++cnt].next=head[u];
e[cnt].to=v;
e[cnt].val=w;
head[u]=cnt;
}
int level[MAXN],s,t;
queue <int> q;
bool bfs()
{
memset(level,-1,sizeof(level));
q.push(s);
level[s]=1;
while (!q.empty())
{
int x=q.front();
q.pop();
for (int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
if (level[v]!=-1 || e[i].val<=0)
continue;
level[v]=level[x]+1;
q.push(v);
}
}
if (level[t]==-1) return false;
return true;
}
int dfs(int x,int f)
{
if (x==t) return f;
int w,tot=0;
for (int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
if (level[v]!=level[x]+1 || e[i].val<=0)
continue;
w=dfs(v,min(e[i].val,f-tot));
e[i].val-=w;
e[i^1].val+=w;
tot+=w;
if (tot==f) return f;
}
if (f-tot) level[x]=-1;
return tot;
}
int max_flow()
{
int ans=0;
while (bfs())
ans+=dfs(s,INF);
return ans;
}
int main()
{
int n,n1,n2;
scanf("%d%d%d",&n,&n1,&n2);
s=0,t=2*n+n1+n2+1;
for (int i=1;i<=n1;i++)
addedge(s,i,1),addedge(i,s,0);
for (int i=1;i<=n2;i++)
addedge(i+n1,t,1),addedge(t,i+n1,0);
int tmp=n1+n2;
for (int i=1;i<=n;i++)
{
int num1,num2;
addedge(i+tmp,i+tmp+n,1),addedge(i+tmp+n,i+tmp,0);
scanf("%d%d",&num1,&num2);
for (int j=1;j<=num1;j++)
{
int x;
scanf("%d",&x);
addedge(x,i+tmp,1);
addedge(i+tmp,x,0);
}
for (int j=1;j<=num2;j++)
{
int x;
scanf("%d",&x);
addedge(i+tmp+n,x+n1,1);
addedge(x+n1,i+tmp+n,0);
}
}
int ans=max_flow();
cout<<ans;
return 0;
}
SGU 438 The Glorious Karlutka River =)
题目大意:有一条东西向流淌的河,宽为 W,河中有 N 块石头,每块石头的坐标(Xi, Yi)和最大承受人数 Ci 已知。现在有 M 个游客在河的南岸,他们想穿越这条河流,但是每个人每次最远只能跳 D 米,每跳一次耗时 1 秒。问他们能否全部穿越这条河流,如果能,最少需要多长时间。
建模思路:传说中的“动态流问题”,在流量限制之外又添加了时间限制。对每个石头的每个时间点进行拆点,再对每个石头拆成入点和出点,两岸分别作为源汇,枚举时间加点,每次在上一秒的残余网络上继续最大流即可,看什么时候流量大于等于总人数即可。
我们可以发现总时间不会超过n+m,极端情况:所有石头排成一列,所有人一个接一个的跳过去。所以枚举时间只需要枚举n+m即可。
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
const int MAXN = 42000;
const int MAXM = 9600000;
const int INF = 0x3f3f3f3f;
struct Edge
{
int from, to, cap, next;
};
struct Point
{
double x, y;
int cap;
};
Edge edge[MAXM];
Edge con[MAXN];
Point p[MAXN];
int head[MAXN];
int headcon[MAXN];
int level[MAXN];
int src, des, cnt, cnt1, ans;
double dist( Point a, Point b )
{
return sqrt( (a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y) );
}
void addcon( int from, int to )
{
con[cnt1].from = from;
con[cnt1].to = to;
con[cnt1].next = headcon[from];
headcon[from] = cnt1++;
swap( from, to );
con[cnt1].from = from;
con[cnt1].to = to;
con[cnt1].next = headcon[from];
headcon[from] = cnt1++;
}
void addedge( int from, int to, int cap )
{
edge[cnt].from = from;
edge[cnt].to = to;
edge[cnt].cap = cap;
edge[cnt].next = head[from];
head[from] = cnt++;
swap( from, to );
edge[cnt].from = from;
edge[cnt].to = to;
edge[cnt].cap = 0;
edge[cnt].next = head[from];
head[from] = cnt++;
}
int bfs( )
{
memset( level, -1, sizeof level );
queue<int> q;
while (!q.empty( ))
q.pop( );
level[src] = 0;
q.push( src );
while (!q.empty( ))
{
int u = q.front( );
q.pop( );
for (int i = head[u]; i != -1; i = edge[i].next)
{
int v = edge[i].to;
if (edge[i].cap > 0 && level[v] == -1)
{
level[v] = level[u] + 1;
q.push( v );
}
}
}
return level[des] != -1;
}
int dfs( int u, int f )
{
if (u == des) return f;
int tem;
for (int i = head[u]; i != -1; i = edge[i].next)
{
int v = edge[i].to;
if (edge[i].cap > 0 && level[v] == level[u] + 1)
{
tem = dfs( v, min( f, edge[i].cap ) );
if (tem > 0)
{
edge[i].cap -= tem;
edge[i ^ 1].cap += tem;
return tem;
}
}
}
level[u] = -1;
return 0;
}
int Dinic( )
{
int ans = 0, tem;
while (bfs( ))
{
while ((tem = dfs( src, INF )) > 0)
{
ans += tem;
}
}
return ans;
}
int main( )
{
int n, m, d, w;
src = 0;
des = 5500;
while (cin >> n >> m >> d >> w)
{
memset( head, -1, sizeof head );
memset( headcon, -1, sizeof headcon );
ans = cnt = cnt1 = 0;
for (int i = 1; i <= n; i++)
{
cin >> p[i].x >> p[i].y >> p[i].cap;
}
if (d >= w)
{
cout << 1 << endl;
continue;
}
for (int i = 1; i <= n; i++)
{
if (p[i].y <= d)
addcon( src, i );
if (w - p[i].y <= d)
addcon( i, des );
for (int j = i + 1; j <= n; j++)
{
if (dist( p[i], p[j] ) <= d)
addcon( i, j );
}
addcon( i, i );
}
int t;
for (t = 1; t <= n + m; t++) // time
{
for (int j = headcon[src]; j != -1; j = con[j].next)
{
int v = con[j].to;
addedge( src, t * 200 + v, INF );
}
for (int k = 1; k <= n; k++) //rocks
{
for (int j = headcon[k]; j != -1; j = con[j].next)
{
int v = con[j].to;
if (v == des)
addedge( (t - 1) * 200 + k + 50, des, INF );
else
addedge( (t - 1) * 200 + k + 50, t * 200 + v, INF );
}
addedge( t * 200 + k, t * 200 + k + 50, p[k].cap );
}
int temp = Dinic( );
ans += temp;
if (ans >= m) break;
}
if (t > n + m)
cout << "IMPOSSIBLE" << endl;
else
cout << t << endl;
}
return 0;
}
SPOJ 287 Smart Network Administrator
题目大意:一座村庄有 N 户人家。只有第一家可以连上互联网,其他人家要想上网必须拉一根缆线通过若干条街道连到第一家。每一根完整的缆线只能有一种颜色。网管有一个要求,各条街道内不同人家的缆线必须不同色,且总的不同颜色种数最小。求在指定的 K 户人家都能上网的前提下,最小的不同颜色种数。
建模思路:先用普通的建图方法:以第一家作为T,K户每个人作为一个点连接(s,i,1),每条街道连边(u,v,INF)和(v,u,INF),用网络流s-t流代表方案的思想,这里的一条流代表一条缆线。
是不是看上去非常的优秀!然而是错的。我们最大流只能使总流量最大,不能保证支流也满足要求。
这时,我们强制街道的流量不是INF,改成一个定值w,此时去跑最大流,若最大流等于K则说明这种方案符合要求,若小于K就说明流量过小。
是不是想到了二分!更加优秀了。
网络流经常与二分结合的qwqqq。
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<cstdlib>
#include<ctime>
#include<map>
#include<queue>
#include<vector>
#include<stack>
#include<set>
#define pa pair<int,int>
#define INF 0x3f3f3f3f
#define inf 0x3f
#define fi first
#define se second
#define mp make_pair
#define ll long long
#define ull unsigned long long
#define pb push_back
using namespace std;
inline ll read()
{
long long f=1,sum=0;
char c=getchar();
while (c<'0' || c>'9')
{
if (c=='-') f=-1;
c=getchar();
}
while (c>='0' && c<='9')
{
sum=sum*10+c-'0';
c=getchar();
}
return sum*f;
}
const int MAXN=1010;
const int MAXM=100010;
struct edge
{
int next,to,val;
};
edge e[MAXM];
int head[MAXN],cnt=1;
void addedge(int u,int v,int w)
{
e[++cnt].next=head[u];
e[cnt].to=v;
e[cnt].val=w;
head[u]=cnt;
}
int level[MAXN],s,t;
queue <int> q;
bool bfs()
{
memset(level,-1,sizeof(level));
level[s]=1;
q.push(s);
while (!q.empty())
{
int x=q.front();
q.pop();
for (int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
if (level[v]!=-1 || e[i].val<=0)
continue;
level[v]=level[x]+1;
q.push(v);
}
}
if (level[t]==-1) return false;
return true;
}
int dfs(int x,int f)
{
if (x==t) return f;
int w,tot=0;
for (int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
if (level[v]!=level[x]+1 || e[i].val<=0)
continue;
w=dfs(v,min(e[i].val,f-tot));
e[i].val-=w;
e[i^1].val+=w;
tot+=w;
if (tot==f) return f;
}
if (f-tot) level[x]=-1;
return tot;
}
int max_flow()
{
int ans=0;
while (bfs())
ans+=dfs(s,INF);
return ans;
}
int u[MAXN],v[MAXN];
int a[MAXN];
int n,m,k;
bool check(int mid)
{
memset(head,0,sizeof(head));
cnt=1;
for (int i=1;i<=k;i++)
addedge(s,a[i],1),addedge(a[i],s,0);
for (int i=1;i<=m;i++)
addedge(u[i],v[i],mid),addedge(v[i],u[i],mid);
int flow=max_flow();
if (flow==k) return true;
return false;
}
int main()
{
int T;
scanf("%d",&T);
while (T--)
{
scanf("%d%d%d",&n,&m,&k);
for (int i=1;i<=k;i++)
scanf("%d",&a[i]);
for (int i=1;i<=m;i++)
scanf("%d%d",&u[i],&v[i]);
int l=1,r=n,mid,ans;
s=0,t=1;
while (l<=r)
{
mid=(l+r)>>1;
if (check(mid))
r=mid-1,ans=mid;
else
l=mid+1;
}
cout<<ans<<endl;
}
return 0;
}
最小割
最小割虽然算法和最大流是一样的,但是建模更加难想,可以解决的问题也更加丰富多样。
HOJ 2634 How to earn more
链链链链链接
题目大意:有 M 个项目和 N 个员工。做项目 i 可以获得 Ai 元,但是必须雇用若干个指定的员工。雇用员工 j 需要花费 Bj 元,且一旦雇用,员工 j 可以参加多个项目的开发。问经过合理的项目取舍,最多能挣多少钱。
建模思路:传说中的“蕴含式最大获利问题”,是最大权闭合子图模型的一种。
最大权闭合子图的建模方法:所有收益与源点连边,所有支出与汇点连边,收益和支出的依赖关系相互连边,边权和减去最小割即可。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstdlib>
#include<map>
#include<vector>
#include<ctime>
#include<stack>
#define mp make_pair
#define pa pair<int,int>
#define INF 0x3f3f3f3f
#define inf 0x3f
#define fi first
#define se second
#define pb push_back
#define ll long long
#define ull unsigned long long
using namespace std;
inline ll read()
{
long long f=1,sum=0;
char c=getchar();
while (c<'0' || c>'9')
{
if (c=='-') f=-1;
c=getchar();
}
while (c>='0' && c<='9')
{
sum=sum*10+c-'0';
c=getchar();
}
return sum*f;
}
const int MAXN=310;
const int MAXM=100010;
struct edge
{
int next,to,val;
};
edge e[MAXM];
int head[MAXN],cnt=1;
void addedge(int u,int v,int w)
{
e[++cnt].next=head[u];
e[cnt].to=v;
e[cnt].val=w;
head[u]=cnt;
}
int level[MAXN],s,t;
queue <int> q;
bool bfs()
{
memset(level,-1,sizeof(level));
q.push(s);
level[s]=1;
while (!q.empty())
{
int x=q.front();
q.pop();
for (int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
if (level[v]!=-1 || e[i].val<=0)
continue;
level[v]=level[x]+1;
q.push(v);
}
}
if (level[t]==-1) return false;
return true;
}
int dfs(int x,int f)
{
if (x==t) return f;
int w,tot=0;
for (int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
if (level[v]!=level[x]+1 || e[i].val<=0)
continue;
w=dfs(v,min(e[i].val,f-tot));
e[i].val-=w;
e[i^1].val+=w;
tot+=w;
if (tot==f) return f;
}
if (f-tot) level[x]=-1;
return tot;
}
int max_flow()
{
int ans=0;
while (bfs())
ans+=dfs(s,INF);
return ans;
}
int main()
{
int T;
scanf("%d",&T);
while (T--)
{
memset(head,0,sizeof(head));
cnt=1;
int n,m;
scanf("%d%d",&n,&m);
s=0,t=n+m+10;
int ans=0;
for (int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
addedge(s,i,x),addedge(i,s,0);
ans+=x;
}
for (int i=1;i<=m;i++)
{
int x;
scanf("%d",&x);
addedge(i+n,t,x),addedge(t,i+n,0);
}
for (int i=1;i<=n;i++)
{
int num;
scanf("%d",&num);
for (int j=1;j<=num;j++)
{
int x;
scanf("%d",&x);
x++;
addedge(i,x+n,INF),addedge(x+n,i,0);
}
}
int flow=max_flow();
ans-=flow;
printf("%d\n",ans);
}
return 0;
}
POJ 1815 Friendship
链接接接接接
题目大意:现代社会人们都靠电话通信。A 与 B 能通信当且仅当 A 知道 B 的电话号或者 A知道 C 的电话号且 C 与 B 能通信。若 A 知道 B 的电话号,那么 B 也知道 A 的电话号。然而不好的事情总是会发生在某些人身上,比如他的电话本丢了,同时他又换了电话号,导致他跟所有人失去了联系。现在给定 N 个人之间的通信关系以及特定的两个人 S 和 T,问最少几个人发生不好的事情可以导致 S 与 T 无法通信并输出这些人。如果存在多组解,输出字典序最小的一组。
思路:求的是一个最小点割集,我们拆点构图:一个人i拆成(i,i’,1),对于每一个关系,连边(i’,j,INF),(j’,i,INF),流量设成无穷可以保证这条边不会被割掉,只会去割拆点后的人(带表这个人发生了不好的事)。
那如何输出字典序最小的方案呢?
我们先求出来最小割,然后编号从小到大枚举删点。如果删去这个点对答案有影响,那么这个点应该是删掉了,从小到大枚举做到了字典序最小。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstdlib>
#include<map>
#include<vector>
#include<ctime>
#include<stack>
#define mp make_pair
#define pa pair<int,int>
#define INF 0x3f3f3f3f
#define inf 0x3f
#define fi first
#define se second
#define pb push_back
#define ll long long
#define ull unsigned long long
using namespace std;
inline ll read()
{
long long f=1,sum=0;
char c=getchar();
while (c<'0' || c>'9')
{
if (c=='-') f=-1;
c=getchar();
}
while (c>='0' && c<='9')
{
sum=sum*10+c-'0';
c=getchar();
}
return sum*f;
}
const int MAXN=2010;
const int MAXM=2000010;
struct edge
{
int next,to,val;
};
edge e[MAXM];
int head[MAXN],cnt=1;
void addedge(int u,int v,int w)
{
e[++cnt].next=head[u];
e[cnt].to=v;
e[cnt].val=w;
head[u]=cnt;
}
int level[MAXN],s,t,n;
queue <int> q;
bool bfs()
{
memset(level,-1,sizeof(level));
level[s]=1;
q.push(s);
while (!q.empty())
{
int x=q.front();
q.pop();
for (int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
if (level[v]!=-1 || e[i].val<=0)
continue;
level[v]=level[x]+1;
q.push(v);
}
}
if (level[t]==-1) return false;
return true;
}
int dfs(int x,int f)
{
if (x==t) return f;
int w,tot=0;
for (int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
if (level[v]!=level[x]+1 || e[i].val<=0)
continue;
w=dfs(v,min(e[i].val,f-tot));
e[i].val-=w;
e[i^1].val+=w;
tot+=w;
if (tot==f) return f;
}
if (f-tot) level[x]=-1;
return tot;
}
int max_flow()
{
int ans=0;
while (bfs())
ans+=dfs(s,INF);
return ans;
}
int a[MAXN][MAXN];
bool vis[MAXN];
void build()
{
memset(head,0,sizeof(head));
cnt=1;
for (int i=1;i<=n;i++)
if (!vis[i])
addedge(i,i+n,1),addedge(i+n,i,0);
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
if (a[i][j])
addedge(i+n,j,INF),addedge(j,i+n,0);
}
vector <int> anss;
int main()
{
scanf("%d%d%d",&n,&s,&t);
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
scanf("%d",&a[i][j]);
if (a[s][t])
{
printf("NO ANSWER!");
return 0;
}
s+=n;
build();
int flow=max_flow();
printf("%d\n",flow);
if (!flow) return 0;
for (int i=1;i<=n;i++)
{
if (i==s-n || i==t) continue;
vis[i]=true;
build();
int tmp=max_flow();
if (tmp<flow)
flow--,anss.push_back(i);
else
vis[i]=false;
if (!flow) break;
}
for (int i=0;i<(int)anss.size()-1;i++)
printf("%d ",anss[i]);
printf("%d",anss[(int)anss.size()-1]);
return 0;
}
SPOJ 839 Optimal Marks
题目大意:给出一个无向图,每个点有一个标号 mark[i],不同点可能有相同的标号。对于
一条边(u, v),它的权值定义为 mark[u] xor mark[v]。现在一些点的标号已定,请
决定剩下点的标号,使得总的边权和最小。
思路:此题出自胡伯涛的论文《最小割模型在信息学竞赛中的应用》。我们可以发现,对于所有的二进制位,他们是互不影响的。所以对于每一个二进制位建图,分别跑最小割。它就变成了一个“二者选其一”式的问题。对于所有标号为1的与s相连,标号为0的与汇点相连,待定的都设为0,有边的01连边,最小割后在残余网络上顺着源点dfs,所有能走到的地方这一位都是1,标记一下即可。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstdlib>
#include<map>
#include<vector>
#include<ctime>
#include<stack>
#define mp make_pair
#define pa pair<int,int>
#define INF 0x3f3f3f3f
#define inf 0x3f
#define fi first
#define se second
#define pb push_back
#define ll long long
#define ull unsigned long long
using namespace std;
inline ll read()
{
long long f=1,sum=0;
char c=getchar();
while (c<'0' || c>'9')
{
if (c=='-') f=-1;
c=getchar();
}
while (c>='0' && c<='9')
{
sum=sum*10+c-'0';
c=getchar();
}
return sum*f;
}
const int MAXN=20010;
const int MAXM=1000010;
struct edge
{
int next,to,val;
};
edge e[MAXM];
int head[MAXN],cnt=1;
void addedge(int u,int v,int w)
{
e[++cnt].next=head[u];
e[cnt].to=v;
e[cnt].val=w;
head[u]=cnt;
}
int level[MAXN],s,t;
queue <int> q;
bool bfs()
{
memset(level,-1,sizeof(level));
q.push(s);
level[s]=1;
while (!q.empty())
{
int x=q.front();
q.pop();
for (int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
if (level[v]!=-1 || e[i].val<=0)
continue;
level[v]=level[x]+1;
q.push(v);
}
}
if (level[t]==-1) return false;
return true;
}
int dfs(int x,int f)
{
if (x==t) return f;
int w,tot=0;
for (int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
if (level[v]!=level[x]+1 || e[i].val<=0)
continue;
w=dfs(v,min(e[i].val,f-tot));
e[i].val-=w;
e[i^1].val+=w;
tot+=w;
if (tot==f) return f;
}
if (f-tot) level[x]=-1;
return tot;
}
int max_flow()
{
int ans=0;
while (bfs())
ans+=dfs(s,INF);
return ans;
}
int a[510][510],n,num[510];
void build(int bit)
{
for (int i=1;i<=n;i++)
{
if (!num[i]) continue;
if (num[i]&(1<<bit))
addedge(s,i,INF),addedge(i,s,0);
else
addedge(i,t,INF),addedge(t,i,0);
}
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
if (a[i][j])
addedge(i,j,1),addedge(j,i,0);
}
int ans[MAXN];
bool visit[MAXN];
void dfs1(int x,int bit)
{
visit[x]=1;
ans[x]|=(1<<bit);
for (int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
if (e[i].val<=0 || visit[v]) continue;
dfs1(v,bit);
}
}
void init()
{
memset(head,0,sizeof(head));
memset(visit,0,sizeof(visit));
cnt=1;
}
int main()
{
int T;
scanf("%d",&T);
while (T--)
{
memset(num,0,sizeof(num));
memset(ans,0,sizeof(ans));
memset(a,0,sizeof(a));
int m;
scanf("%d%d",&n,&m);
s=0,t=n+10;
for (int i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
a[u][v]=a[v][u]=1;
}
int k;
scanf("%d",&k);
for (int i=1;i<=k;i++)
{
int id,x;
scanf("%d%d",&id,&x);
num[id]=x;
}
for (int i=0;i<=31;i++)
{
init();
build(i);
max_flow();
dfs1(s,i);
}
for (int i=1;i<=n;i++)
printf("%d\n",ans[i]);
}
return 0;
}
SPOJ 1693 Coconuts
题目大意:有趣的题干。。N个城堡守卫正在为非洲的燕子能否搬运椰子而进行投票。每个人都可以持赞成或者反对。每个人都有几个朋友,他不希望和他的朋友的票是不一样的,所以每个人可以投出“违心”的票,问每个人在经过抉择后,违心的票数和持不同意见的朋友对数的和最少。
思路:又是一个“二者选其一”的问题,非常经典的建图。所有投赞成的守卫连边(s,i,1),反对票连边(i,t,1),代表投赞成票和反对票的人改变自己意愿的花费都是1,再对所有朋友连边(i,j,1),(j,i,1),代表若两人分属不同观点(一个连接s一个连接t),花费是1,做出最小割即为答案。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstdlib>
#include<map>
#include<vector>
#include<ctime>
#include<stack>
#define mp make_pair
#define pa pair<int,int>
#define INF 0x3f3f3f3f
#define inf 0x3f
#define fi first
#define se second
#define pb push_back
#define ll long long
#define ull unsigned long long
using namespace std;
inline ll read()
{
long long f=1,sum=0;
char c=getchar();
while (c<'0' || c>'9')
{
if (c=='-') f=-1;
c=getchar();
}
while (c>='0' && c<='9')
{
sum=sum*10+c-'0';
c=getchar();
}
return sum*f;
}
const int MAXN=510;
const int MAXM=1000010;
struct edge
{
int next,to,val;
};
edge e[MAXM];
int head[MAXN],cnt=1;
void addedge(int u,int v,int w)
{
e[++cnt].next=head[u];
e[cnt].to=v;
e[cnt].val=w;
head[u]=cnt;
}
int level[MAXN],s,t;
queue <int> q;
bool bfs()
{
memset(level,-1,sizeof(level));
q.push(s);
level[s]=1;
while (!q.empty())
{
int x=q.front();
q.pop();
for (int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
if (level[v]!=-1 || e[i].val<=0)
continue;
level[v]=level[x]+1;
q.push(v);
}
}
if (level[t]==-1) return false;
return true;
}
int dfs(int x,int f)
{
if (x==t) return f;
int w,tot=0;
for (int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
if (level[v]!=level[x]+1 || e[i].val<=0)
continue;
w=dfs(v,min(e[i].val,f-tot));
e[i].val-=w;
e[i^1].val+=w;
tot+=w;
if (tot==f) return f;
}
if (f-tot) level[x]=-1;
return tot;
}
int max_flow()
{
int ans=0;
while (bfs())
ans+=dfs(s,INF);
return ans;
}
int main()
{
while (1)
{
memset(head,0,sizeof(head));
cnt=1;
int n,m;
scanf("%d%d",&n,&m);
s=0,t=n+1;
if (n+m==0) break;
for (int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
if (x) addedge(s,i,1),addedge(i,s,0);
else addedge(i,t,1),addedge(t,i,0);
}
for (int i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
addedge(u,v,1),addedge(v,u,0);
addedge(v,u,1),addedge(u,v,0);
}
int cut=max_flow();
printf("%d\n",cut);
}
return 0;
}
Codeforces GYM 100729F
题目大意:你有一个n*n的网格,每个网格可以是坑或土,有一个人请你把这块地修建成一个“pool”。你有如下操作:1.把这块地保持原来的样子,无花费。2.把土挖掉变成坑,花费d。3.把坑填上土,花费f。4.在土和坑的边界加上一层边界,花费b。在最后将会在所有的坑中填上水,成为一个pool,所以整个网格最外面的一圈必须是土,求使这块地变成一个pool的花费最小(坑的面积可以是0)。
思路:第一直觉,这是一个最小割,简直是coconut的翻版,但是往模型上一套,发现貌似有点问题:那道题的花费都是1,这里有三个不同的花费,怎么代表选或者不选呢。我一转念就把它放弃了。。。。
转投我的第二直觉,既然是费用,那就费用流吧。写啊写啊写啊写。。。发现没法限制,拆点!还有问题,继续拆!结果我一个点拆了六个点去跑,依然有问题。
回来继续想。又看到了edelweiss大神的《网络流建模汇总》中的一段话
二者取其一式问题:个人认为这是最小割次 NB 的应用。这类建模方法也有一个明显的特点,就是每个点都可以有两种方案供选择,每种方案都有一个花费,必须且只能选择其中一种;另外如果某两个点选择了不同的方案,还要在这两个点之间增加额外的费用。这种应用其实也可算作第一种构图法,只不过它的特点更明显,所以我把它单列出来。
再回去想这个题,与这个模型完美的契合。这就是coconut那个题的建模方法。
建图方法就一目了然了:所有坑与s连边(s,i,f),土与汇点连边(i,t,d),四联通的点连边(i,j,b),(j,i,b),最外围的一圈因为必须是土,所以提前算出,然后最小割。结束。
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<cstdlib>
#include<ctime>
#include<map>
#include<queue>
#include<vector>
#include<stack>
#include<set>
#define pa pair<int,int>
#define INF 0x3f3f3f3f
#define inf 0x3f
#define fi first
#define se second
#define mp make_pair
#define ll long long
#define ull unsigned long long
#define pb push_back
using namespace std;
inline ll read()
{
long long f=1,sum=0;
char c=getchar();
while (c<'0' || c>'9')
{
if (c=='-') f=-1;
c=getchar();
}
while (c>='0' && c<='9')
{
sum=sum*10+c-'0';
c=getchar();
}
return sum*f;
}
const int MAXN=3010;
const int MAXM=100010;
struct edge
{
int next,to,val;
};
edge e[MAXM];
int head[MAXN],cnt=1;
void addedge(int u,int v,int w)
{
e[++cnt].next=head[u];
e[cnt].to=v;
e[cnt].val=w;
head[u]=cnt;
swap(u,v);
e[++cnt].next=head[u];
e[cnt].to=v;
e[cnt].val=0;
head[u]=cnt;
}
int level[MAXN],s,t;
queue <int> q;
bool bfs()
{
memset(level,-1,sizeof(level));
q.push(s);
level[s]=1;
while (!q.empty())
{
int x=q.front();
q.pop();
for (int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
if (level[v]!=-1 || e[i].val<=0)
continue;
level[v]=level[x]+1;
q.push(v);
}
}
if (level[t]==-1) return false;
return true;
}
int dfs(int x,int f)
{
if (x==t) return f;
int w,tot=0;
for (int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
if (level[v]!=level[x]+1 || e[i].val<=0)
continue;
w=dfs(v,min(e[i].val,f-tot));
e[i].val-=w;
e[i^1].val+=w;
tot+=w;
if (tot==f) return f;
}
if (f-tot) level[x]=-1;
return tot;
}
int max_flow()
{
int ans=0;
while (bfs())
ans+=dfs(s,INF);
return ans;
}
char c[55][55];
int n,m;
int P(int x,int y)
{
return (x-1)*m+y;
}
void init()
{
memset(head,0,sizeof(head));
cnt=1;
}
const int dx[4]={-1,0,1,0};
const int dy[4]={0,1,0,-1};
int main()
{
int T;
scanf("%d",&T);
while (T--)
{
init();
int d,b,f;
scanf("%d%d",&m,&n);
scanf("%d%d%d",&d,&f,&b);
for (int i=1;i<=n;i++)
scanf("%s",c[i]+1);
int tot=0;
s=0,t=MAXN-10;
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
{
int p=P(i,j);
if (i==1 || j==1 || i==n || j==m)
tot+=(c[i][j]=='.')*f,addedge(s,p,INF);
else if (c[i][j]=='.')
addedge(p,t,f);
else
addedge(s,p,d);
for (int k=0;k<4;k++)
{
int nx=i+dx[k],ny=j+dy[k];
int np=P(nx,ny);
if (nx<=0 || nx>n || ny<=0 || ny>m)
continue;
addedge(p,np,b);
}
}
tot+=max_flow();
cout<<tot<<endl;
}
return 0;
}
上下界网络流
做的题不多,说一个。。。
POJ 2396 Budget
请出链接君
题目大意:一个 M*N 的矩阵,给定每行、每列的元素之和 Ri、Cj 以及 c 个满足下列形式的约束条件:i j {< = >} k(i, j 表示第 i 行第 j 列的元素,为 0 表示整列或整行),问是否存在满足所有约束条件的矩阵。
建模思路:有源汇上下界可行流问题。
1.把每一行看做一个点, 把每一列看做一个点。
2.建立一个源点s,连接s与每一行,容量上限下限设为该行和。
3.建立一个汇点t,连接每一列与t,容量上限下限设为该列和。
4.对于每一行跟每一列,先连一条下限为0,上限为无穷大的边,然后根据给出的条件修改边上下界。
这就是一个有源汇的上下界网络流,转化成无源汇的上下界网络流:从t向s连一条INF的边,使流量守恒即可。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstdlib>
#include<map>
#include<vector>
#include<ctime>
#include<stack>
#define mp make_pair
#define pa pair<int,int>
#define INF 0x3f3f3f3f
#define inf 0x3f
#define fi first
#define se second
#define pb push_back
#define ll long long
#define ull unsigned long long
using namespace std;
inline ll read()
{
long long f=1,sum=0;
char c=getchar();
while (c<'0' || c>'9')
{
if (c=='-') f=-1;
c=getchar();
}
while (c>='0' && c<='9')
{
sum=sum*10+c-'0';
c=getchar();
}
return sum*f;
}
const int MAXN=10010;
const int MAXM=1000010;
struct edge
{
int next,to,val;
};
edge e[MAXM];
int head[MAXN],cnt=1;
void addedge(int u,int v,int w)
{
e[++cnt].next=head[u];
e[cnt].to=v;
e[cnt].val=w;
head[u]=cnt;
}
int level[MAXN],s,t,ss,tt;
queue <int> q;
bool bfs()
{
memset(level,-1,sizeof(level));
q.push(s);
level[s]=1;
while (!q.empty())
{
int x=q.front();
q.pop();
for (int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
if (level[v]!=-1 || e[i].val<=0)
continue;
level[v]=level[x]+1;
q.push(v);
}
}
if (level[t]==-1) return false;
return true;
}
int dfs(int x,int f)
{
if (x==t) return f;
int w,tot=0;
for (int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
if (level[v]!=level[x]+1 || e[i].val<=0)
continue;
w=dfs(v,min(e[i].val,f-tot));
e[i].val-=w;
e[i^1].val+=w;
tot+=w;
if (tot==f) return f;
}
if (f-tot) level[x]=-1;
return tot;
}
int n,m,up[250][250],down[250][250],tot;
int id[250][250];
int max_flow()
{
int ans=0;
while (bfs())
ans+=dfs(s,INF);
return ans==tot;
}
void init()
{
memset(head,0,sizeof(head));
cnt=1;
memset(down,0,sizeof(down));
memset(up,inf,sizeof(up));
memset(id,0,sizeof(id));
}
bool build()
{
addedge(tt,ss,INF),addedge(ss,tt,0);
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
if (down[i][j]>up[i][j]) return 0;
else
{
addedge(i,n+j,up[i][j]-down[i][j]),addedge(n+j,i,0);
tot+=down[i][j];
id[i][j]=cnt-1;
addedge(s,n+j,down[i][j]),addedge(n+j,s,0);
addedge(i,t,down[i][j]),addedge(t,i,0);
}
return 1;
}
int main()
{
int T;
scanf("%d",&T);
while (T--)
{
scanf("%d%d",&n,&m);
s=0,t=n+m+10,ss=n+m+1,tt=n+2+m;
init();
int tot1=0,tot2=0;
for (int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
tot1+=x;
addedge(s,i,x),addedge(i,s,0);
addedge(ss,t,x),addedge(t,ss,0);
}
for (int i=1;i<=m;i++)
{
int x;
scanf("%d",&x);
tot2+=x;
addedge(s,tt,x),addedge(tt,s,0);
addedge(i+n,t,x),addedge(t,i+n,0);
}
tot=tot1+tot2;
int k;
scanf("%d",&k);
while (k--)
{
int x,y,num; char sss[5];
scanf("%d%d%s%d",&x,&y,sss+1,&num);
int x1,y1,x2,y2;
x1=x2=x,y1=y2=y;
if (!x) x1=1,x2=n;
if (!y) y1=1,y2=m;
for (int i=x1;i<=x2;i++)
for (int j=y1;j<=y2;j++)
if (sss[1]=='=')
down[i][j]=max(down[i][j],num),up[i][j]=min(up[i][j],num);
else if (sss[1]=='>')
down[i][j]=max(down[i][j],num+1);
else
up[i][j]=min(up[i][j],num-1);
}
if (tot1!=tot2 || !build() || !max_flow())
printf("IMPOSSIBLE\n");
else
{
for (int i=1;i<=n;i++)
{
printf("%d",down[i][1]+e[id[i][1]^1].val);
for (int j=2;j<=m;j++)
printf(" %d",down[i][j]+e[id[i][j]^1].val);
putchar('\n');
}
}
putchar('\n');
}
return 0;
}
费用流
费用流的模型较多,也更加的灵活,每个题都有自己的特点,列出几个比较独特的来剖析一下。
HOJ 2543 Stone IV
题目大意:在无向图 G 中,wywcgs 要从源点 s 购买一些石头并运到汇点 t。每块石头单价是P 元。每条边 i 有一个初始容量 Ci,当容量超过 Ci 时,每增加一单位容量要额外花费 Ei 元。wywcgs 现在手头只有 C 元,问他最多能买多少块石头并成功运到目的地。
思路:经典的“凸费用流问题”——费用随着流的增大呈分段的线性状态。我们采用拆边的方式来解决这个问题:(u,v)拆成(u, v, Ci, 0), (u, v, INF, Ei),根据费用流原理,一定会先选择前一条边走,故拆边成功,跑普通费用流即可。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstdlib>
#include<map>
#include<vector>
#include<ctime>
#include<stack>
#define mp make_pair
#define pa pair<int,int>
#define INF 0x3f3f3f3f
#define inf 0x3f
#define fi first
#define se second
#define pb push_back
#define ll long long
#define ull unsigned long long
using namespace std;
inline ll read()
{
long long f=1,sum=0;
char c=getchar();
while (c<'0' || c>'9')
{
if (c=='-') f=-1;
c=getchar();
}
while (c>='0' && c<='9')
{
sum=sum*10+c-'0';
c=getchar();
}
return sum*f;
}
const int MAXN=10010;
const int MAXM=100010;
struct edge
{
int next,to,val,cost,from;
};
edge e[MAXM];
int head[MAXN],cnt=1;
void addedge(int u,int v,int w,int cost)
{
e[++cnt].next=head[u];
e[cnt].to=v;
e[cnt].val=w;
e[cnt].cost=cost;
e[cnt].from=u;
head[u]=cnt;
}
queue <int> q;
bool inqueue[MAXN];
int dis[MAXN],from[MAXN],s,t;
bool spfa()
{
memset(dis,inf,sizeof(dis));
dis[s]=0,inqueue[s]=1;
q.push(s);
while (!q.empty())
{
int x=q.front();
q.pop();
for (int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
if (dis[v]>dis[x]+e[i].cost && e[i].val)
{
dis[v]=dis[x]+e[i].cost;
from[v]=i;
if (!inqueue[v])
q.push(v),inqueue[v]=1;
}
}
inqueue[x]=0;
}
if (dis[t]<INF) return 1;
return 0;
}
int main()
{
int T;
scanf("%d",&T);
while (T--)
{
memset(head,0,sizeof(head));
cnt=1;
s=0,t=1;
int n,m,tot,p;
scanf("%d%d%d%d",&n,&m,&tot,&p);
for (int i=1;i<=m;i++)
{
int u,v,w,c;
scanf("%d%d%d%d",&u,&v,&w,&c);
addedge(u,v,w,0);
addedge(v,u,0,0);
addedge(u,v,INF,c);
addedge(v,u,0,-c);
}
int ans=0;
while (spfa())
{
ll ans1=0;
int flow=INF;
for (int i=t;i!=s;i=e[from[i]].from)
flow=min(flow,e[from[i]].val);
for (int i=t;i!=s;i=e[from[i]].from)
{
e[from[i]].val-=flow;
e[from[i]^1].val+=flow;
}
ans1+=flow*dis[t];
if (tot>(ll)ans1+(ll)p*flow)
{
tot-=ans1+p*flow;
ans+=flow;
}
else
{
ans+=tot/(dis[t]+p);
break;
}
}
printf("%d\n",ans);
}
return 0;
}
HOJ 2715 Matirx3
题目大意:一个 N*N 的网格,每个单元都有一个价值 Vi 的宝物和一个高度 Hi。现在要作至多 K 次旅行,每次旅行如下:他可以借助的直升机飞到任意一个单元,之后他每次只能向相邻的且高度比当前所在格子低的格子移动。当他移动到一个边界的格子上时,他可以跳出这个网格并完成一次旅行。旅行中所到之处的宝物他可以全部拿走,一旦拿走原来的格子里就没有宝物了。问他最多能拿走价值多少的宝物。
思路:和上题一样,我们拆点的同时拆边,并加边(i,i’,1,-Vi), (i,i’,INF,0),(s,i’,INF,0)。对四联通的格子,若Hi>Hj,则加边(i’,j,INF,0),若格子i在边界上则加边(i’,t,INF,0)。限制增广次数小于等于K。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstdlib>
#include<map>
#include<vector>
#include<ctime>
#include<stack>
#define mp make_pair
#define pa pair<int,int>
#define INF 0x3f3f3f3f
#define inf 0x3f
#define fi first
#define se second
#define pb push_back
#define ll long long
#define ull unsigned long long
using namespace std;
inline ll read()
{
long long f=1,sum=0;
char c=getchar();
while (c<'0' || c>'9')
{
if (c=='-') f=-1;
c=getchar();
}
while (c>='0' && c<='9')
{
sum=sum*10+c-'0';
c=getchar();
}
return sum*f;
}
const int MAXN=10010;
const int MAXM=100010;
struct edge
{
int next,to,val,cost,from;
};
edge e[MAXM];
int head[MAXN],cnt=1;
void addedge(int u,int v,int w,int cost)
{
e[++cnt].next=head[u];
e[cnt].to=v;
e[cnt].val=w;
e[cnt].from=u;
e[cnt].cost=cost;
head[u]=cnt;
}
queue <int> q;
int dis[MAXN],from[MAXN],s,t;
bool inqueue[MAXN];
bool spfa()
{
memset(dis,inf,sizeof(dis));
dis[s]=0;
q.push(s);
inqueue[s]=1;
while (!q.empty())
{
int x=q.front();
q.pop();
for (int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
if (e[i].val && dis[v]>dis[x]+e[i].cost)
{
dis[v]=dis[x]+e[i].cost;
from[v]=i;
if (!inqueue[v])
inqueue[v]=1,q.push(v);
}
}
inqueue[x]=0;
}
if (dis[t]<INF) return 1;
return 0;
}
int min_cost_flow()
{
int flow=INF;
for (int i=t;i!=s;i=e[from[i]].from)
flow=min(flow,e[from[i]].val);
for (int i=t;i!=s;i=e[from[i]].from)
{
e[from[i]].val-=flow;
e[from[i]^1].val+=flow;
int v=e[from[i]].to;
if (v>9) v-=9;
}
return -dis[t];
}
const int dx[4]={-1,0,1,0};
const int dy[4]={0,-1,0,1};
int n,a[55][55],h[55][55];
int pos(int x,int y)
{
return (x-1)*n+y;
}
void build()
{
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
{
int p=pos(i,j),p1=p+n*n;
addedge(p,p1,1,-a[i][j]),addedge(p1,p,0,a[i][j]);
addedge(p,p1,INF,0),addedge(p,p1,0,0);
addedge(s,p,INF,0),addedge(p,s,0,0);
for (int k=0;k<4;k++)
{
int nx=i+dx[k],ny=j+dy[k];
if (nx<=0 || ny<=0 || nx>n || ny>n) continue;
int p2=pos(nx,ny);
if (h[i][j]>h[nx][ny])
addedge(p1,p2,INF,0),addedge(p2,p1,0,0);
}
if (i==1 || j==1 || i==n || j==n)
addedge(p1,t,INF,0),addedge(t,p1,0,0);
}
}
int main()
{
int T;
scanf("%d",&T);
while (T--)
{
memset(head,0,sizeof(head));
cnt=1;
int k;
scanf("%d%d",&n,&k);
s=0,t=2*n*n+1;
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
scanf("%d",&a[i][j]);
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
scanf("%d",&h[i][j]);
build();
int num=0,ans=0;
while (spfa())
{
num++;
ans+=min_cost_flow();
if (num==k) break;
}
printf("%d\n",ans);
}
return 0;
}
HOJ 2739 The Chinese Postman Problem
题目大意:带权有向图上的中国邮路问题:一名邮递员需要经过每条有向边至少一次,最后回到出发点,一条边多次经过权值要累加,问最小总权值是多少。
思路:先对原图判断是否联通,或有无一个点的出入度为0就无解。设一个点入度和出度的差x。若x>0,加边(s,i,x,0),否则加边(i,t,-x,0).对于原图中的每条边,加边(i,j,INF,val[i][j]).求一次费用流,费用加上原图所有边权即可。
代码
#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstdlib>
#include<map>
#include<vector>
#include<ctime>
#include<stack>
#define mp make_pair
#define pa pair<int,int>
#define INF 0x3f3f3f3f
#define inf 0x3f
#define fi first
#define se second
#define pb push_back
#define ll long long
#define ull unsigned long long
using namespace std;
inline ll read()
{
long long f=1,sum=0;
char c=getchar();
while (c<'0' || c>'9')
{
if (c=='-') f=-1;
c=getchar();
}
while (c>='0' && c<='9')
{
sum=sum*10+c-'0';
c=getchar();
}
return sum*f;
}
const int MAXN=10010;
const int MAXM=100010;
struct edge
{
int next,to,val,cost,from;
};
edge e[MAXM];
int head[MAXN],cnt=1;
void addedge(int u,int v,int w,int cost)
{
e[++cnt].next=head[u];
e[cnt].to=v;
e[cnt].val=w;
e[cnt].from=u;
e[cnt].cost=cost;
head[u]=cnt;
}
queue <int> q;
int dis[MAXN],from[MAXN],s,t;
bool inqueue[MAXN];
bool spfa()
{
memset(dis,inf,sizeof(dis));
dis[s]=0;
q.push(s);
inqueue[s]=1;
while (!q.empty())
{
int x=q.front();
q.pop();
for (int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
if (e[i].val && dis[v]>dis[x]+e[i].cost)
{
dis[v]=dis[x]+e[i].cost;
from[v]=i;
if (!inqueue[v])
q.push(v),inqueue[v]=1;
}
}
inqueue[x]=0;
}
if (dis[t]<INF) return 1;
return 0;
}
int min_cost_flow()
{
int flow=INF;
for (int i=t;i!=s;i=e[from[i]].from)
flow=min(flow,e[from[i]].val);
for (int i=t;i!=s;i=e[from[i]].from)
{
e[from[i]].val-=flow;
e[from[i]^1].val+=flow;
}
return dis[t];
}
int in[110],out[110],a[110][110],u[2010],v[2010],w[2010];
bool visit[MAXN];
int n;
void dfs(int x)
{
visit[x]=1;
for (int i=1;i<=n;i++)
{
if (visit[i]) continue;
if (a[x][i]) dfs(i);
}
}
int main()
{
int T;
scanf("%d",&T);
while (T--)
{
memset(head,0,sizeof(head));
cnt=1;
memset(a,0,sizeof(a));
memset(visit,0,sizeof(visit));
memset(in,0,sizeof(in));
memset(out,0,sizeof(out));
int m,ans=0;
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++)
{
scanf("%d%d%d",&u[i],&v[i],&w[i]);
u[i]++,v[i]++;
in[v[i]]++,out[u[i]]++;
ans+=w[i];
a[u[i]][v[i]]=1;
}
dfs(1);
bool flag=false;
for (int i=1;i<=n;i++)
if (!in[i] || !out[i] || !visit[i])
{
cout<<-1<<endl;
flag=true;
break;
}
if (flag) continue;
s=0,t=n+1;
for (int i=1;i<=n;i++)
{
int x=in[i]-out[i];
if (x>0)
addedge(s,i,x,0),addedge(i,s,0,0);
else if (x<0)
addedge(i,t,-x,0),addedge(t,i,0,0);
}
for (int i=1;i<=m;i++)
addedge(u[i],v[i],INF,w[i]),addedge(v[i],u[i],0,-w[i]);
while (spfa())
ans+=min_cost_flow();
printf("%d\n",ans);
}
return 0;
}