网络流提高专题(洛谷题单)
这份题单UVA的题目偏多。但是洛谷不知道为啥UVA交不上去。所以一般我都是去VJ上刷题。
较为简单的最大流模板题:#
The Grand Dinner#
这道题就是上一份题单里的圆桌问题。改改输出就好了。

#pragma GCC optimize(2) #include <bits/stdc++.h> using namespace std; #define pii pair<long long, int> typedef long long ll; const int maxn = 1e6 + 10; const int maxm = 1e5 * 2 + 10; const ll inf = 1e18; const int mod = 1e9 + 7; struct Edge{ int to; int next; ll w; } edge[maxn*2]; int head[maxn], node_num; int cur[maxn];// int n, m, S, T, k;// int dis[maxn];//Bf int cnt = 0;// bool vis[maxn]; inline void init(){ cnt = 0; for (int i = 0; i <= node_num; ++ i) head[i] = -1; } inline void Add_Edge(int u, int v, ll w){ edge[cnt].next = head[u]; edge[cnt].to = v; edge[cnt].w = w; head[u] = cnt++; edge[cnt].next = head[v]; edge[cnt].to = u; edge[cnt].w = 0; head[v] = cnt++; } inline bool Bfs(){ for(int i = 0; i <= node_num; ++i) dis[i] = -1; dis[S] = 0; queue<int> q; q.push(S); 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(dis[v] == -1 && edge[i].w){ dis[v] = dis[u] + 1; q.push(v); } } } return dis[T] != -1; } inline ll dfs(int u, ll flow){ if(u == T) return flow; ll del = flow; for(int i = cur[u]; i != -1; i = edge[i].next){ cur[u] = edge[i].next; int v = edge[i].to; if (dis[v] == dis[u] + 1 && edge[i].w > 0){ ll ans = dfs(v, min(del, edge[i].w)); edge[i].w -= ans; edge[i ^ 1].w += ans; del -= ans; if(del == 0) break; } } return flow - del; } inline ll Dinic() { ll ans = 0; while(Bfs()) { for(int i = 0; i <= node_num; ++i) cur[i] = head[i]; ans += dfs(S, inf); } return ans; } int main() { while (~scanf("%d %d", &m, &n)) { if (!m && !n) break; S = 0, T = n+m+1; node_num = T; init(); int rec_n[n+1], rec_m[m+1]; int sum = 0; for (int i = 1; i <= m; ++ i) { scanf("%d", &rec_m[i]); sum += rec_m[i]; Add_Edge(S, i, rec_m[i]); for (int j = 1; j <= n; ++ j) Add_Edge(i, j+m, 1); } for (int i = 1; i <= n; ++ i) { scanf("%d", &rec_n[i]); Add_Edge(i+m, T, rec_n[i]); } int flag = 0; if (Dinic() == sum) flag = 1; cout << flag << endl; if (flag) { vector<int> rec[m+1]; for (int u = 1; u <= m; ++ u) { for (int j = head[u]; ~j; j = edge[j].next) { if (edge[j].w == 0) { rec[u].push_back(edge[j].to-m); } } } for (int i = 1; i <= m; ++ i) { cout << rec[i][0]; for (int j = 1; j < rec[i].size(); ++ j) cout << " " << rec[i][j]; cout << endl; } } } return 0; }
Crimewave#
题意:给你n个点,问你是否存在n条连向矩阵外的不相交路径。
做法:将每个点拆成两个点。流量为1,能到矩阵外的点连T,开始n个点连S;跑最大流判断是否等于n即可。

#pragma GCC optimize(2) #include <bits/stdc++.h> using namespace std; #define pii pair<long long, int> typedef long long ll; const int maxn = 1e6 + 10; const int maxm = 1e5 * 2 + 10; const ll inf = 1e18; const int mod = 1e9 + 7; struct Edge{ int to; int next; ll w; } edge[maxn*2]; int head[maxn], node_num; int cur[maxn];//当前弧优化数组 int n, m, S, T, k;//点数,边数,源点,汇点 int dis[maxn];//Bfs深度 int cnt = 0;//边数 bool vis[maxn]; inline void init(){ cnt = 0; for (int i = 0; i <= node_num; ++ i) head[i] = -1; } inline void Add_Edge(int u, int v, ll w){ edge[cnt].next = head[u]; edge[cnt].to = v; edge[cnt].w = w; head[u] = cnt++; edge[cnt].next = head[v]; edge[cnt].to = u; edge[cnt].w = 0; head[v] = cnt++; } inline bool Bfs(){ for(int i = 0; i <= node_num; ++i) dis[i] = -1; //这里要根据你所建的点数来确定 dis[S] = 0; queue<int> q; q.push(S); 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(dis[v] == -1 && edge[i].w){//没有标记深度并且有残量 dis[v] = dis[u] + 1; q.push(v); } } } return dis[T] != -1; } inline ll dfs(int u, ll flow){ if(u == T) return flow; ll del = flow; for(int i = cur[u]; i != -1; i = edge[i].next){ cur[u] = edge[i].next;//当前弧优化,下次直接从cur[u]开始增广,节省时间 int v = edge[i].to; if (dis[v] == dis[u] + 1 && edge[i].w > 0){//深度+1且残量大于0 ll ans = dfs(v, min(del, edge[i].w));//木桶原理 edge[i].w -= ans;//正向弧减增广流量 edge[i ^ 1].w += ans;//反向弧加增广流量 del -= ans;//总流量减增广流量 if(del == 0) break;//总流量为0则不继续增广 } } return flow - del;//返回本次增广的流量 } inline ll Dinic() { ll ans = 0; while(Bfs()) { for(int i = 0; i <= node_num; ++i) cur[i] = head[i]; ans += dfs(S, inf); } return ans; } int dic[4][2] = {1,0, -1,0, 0,-1, 0,1}; int main() { int Ti; scanf("%d", &Ti); while (Ti--) { scanf("%d %d %d", &n, &m, &k); S = 0, T = 2*n*m+1; node_num = T; init(); int sum = 0; for (int i = 1, u, v; i <= k; ++ i) { scanf("%d %d", &u, &v); int id = (u-1)*m+v; Add_Edge(S, id, inf); } for (int i = 1; i <= n; ++ i) { for (int j = 1; j <= m; ++ j) { int id = (i-1)*m+j; Add_Edge(id, id+n*m, 1); for (int k = 0; k < 4; ++ k) { int tx = i + dic[k][0], ty = j + dic[k][1]; int id1 = (tx-1)*m+ty; if (tx < 1 || ty < 1 || tx > n || ty > m) { Add_Edge(id+n*m, T, inf); continue; } Add_Edge(id+n*m, id1, inf); } } } if (Dinic() == k) cout << "possible" << endl; else cout << "not possible" << endl; } return 0; }
Internet Bandwidth#
题目大意:单源单汇无向网络求最大流。

#pragma GCC optimize(2) #include <bits/stdc++.h> using namespace std; #define pii pair<long long, int> typedef long long ll; const int maxn = 1e6 + 10; const int maxm = 1e5 * 2 + 10; const ll inf = 1e18; const int mod = 1e9 + 7; struct Edge{ int to; int next; ll w; } edge[maxn*2]; int head[maxn], node_num; int cur[maxn];//当前弧优化数组 int n, m, S, T, k;//点数,边数,源点,汇点 int dis[maxn];//Bfs深度 int cnt = 0;//边数 bool vis[maxn]; inline void init(){ cnt = 0; for (int i = 0; i <= node_num; ++ i) head[i] = -1; } inline void Add_Edge(int u, int v, ll w){ edge[cnt].next = head[u]; edge[cnt].to = v; edge[cnt].w = w; head[u] = cnt++; edge[cnt].next = head[v]; edge[cnt].to = u; edge[cnt].w = 0; head[v] = cnt++; } inline bool Bfs(){ for(int i = 0; i <= node_num; ++i) dis[i] = -1; //这里要根据你所建的点数来确定 dis[S] = 0; queue<int> q; q.push(S); 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(dis[v] == -1 && edge[i].w){//没有标记深度并且有残量 dis[v] = dis[u] + 1; q.push(v); } } } return dis[T] != -1; } inline ll dfs(int u, ll flow){ if(u == T) return flow; ll del = flow; for(int i = cur[u]; i != -1; i = edge[i].next){ cur[u] = edge[i].next;//当前弧优化,下次直接从cur[u]开始增广,节省时间 int v = edge[i].to; if (dis[v] == dis[u] + 1 && edge[i].w > 0){//深度+1且残量大于0 ll ans = dfs(v, min(del, edge[i].w));//木桶原理 edge[i].w -= ans;//正向弧减增广流量 edge[i ^ 1].w += ans;//反向弧加增广流量 del -= ans;//总流量减增广流量 if(del == 0) break;//总流量为0则不继续增广 } } return flow - del;//返回本次增广的流量 } inline ll Dinic() { ll ans = 0; while(Bfs()) { for(int i = 0; i <= node_num; ++i) cur[i] = head[i]; ans += dfs(S, inf); } return ans; } int dic[4][2] = {1,0, -1,0, 0,-1, 0,1}; int main() { int tot = 0; while (~scanf("%d", &n) && n) { scanf("%d %d %d", &S, &T, &m); //S = 0, T = 2*n*m+1; node_num = n; init(); int sum = 0; for (int i = 1, u, v, cap; i <= m; ++ i) { scanf("%d %d %d", &u, &v, &cap); Add_Edge(u, v, cap), Add_Edge(v, u, cap); } //cout << Dinic() << endl; cout << "Network " << ++tot << endl; cout << "The bandwidth is " << Dinic() << "." << endl << endl; } return 0; }
Air Raid#
有向无环图(DAG)的最小路径覆盖的模板题。

#pragma GCC optimize(2) #include <bits/stdc++.h> using namespace std; #define pii pair<long long, int> typedef long long ll; const int maxn = 1e6 + 10; const int maxm = 1e5 * 2 + 10; const ll inf = 1e18; const int mod = 1e9 + 7; struct Edge{ int to; int next; ll w; } edge[maxn*2]; int head[maxn], node_num; int cur[maxn];//当前弧优化数组 int n, m, S, T, k;//点数,边数,源点,汇点 int dis[maxn];//Bfs深度 int cnt = 0;//边数 bool vis[maxn]; inline void init(){ cnt = 0; for (int i = 0; i <= node_num; ++ i) head[i] = -1; } inline void Add_Edge(int u, int v, ll w){ edge[cnt].next = head[u]; edge[cnt].to = v; edge[cnt].w = w; head[u] = cnt++; edge[cnt].next = head[v]; edge[cnt].to = u; edge[cnt].w = 0; head[v] = cnt++; } inline bool Bfs(){ for(int i = 0; i <= node_num; ++i) dis[i] = -1; //这里要根据你所建的点数来确定 dis[S] = 0; queue<int> q; q.push(S); 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(dis[v] == -1 && edge[i].w){//没有标记深度并且有残量 dis[v] = dis[u] + 1; q.push(v); } } } return dis[T] != -1; } inline ll dfs(int u, ll flow) { if(u == T) return flow; ll del = flow; for(int i = cur[u]; i != -1; i = edge[i].next){ cur[u] = edge[i].next;//当前弧优化,下次直接从cur[u]开始增广,节省时间 int v = edge[i].to; if (dis[v] == dis[u] + 1 && edge[i].w > 0){//深度+1且残量大于0 ll ans = dfs(v, min(del, edge[i].w));//木桶原理 edge[i].w -= ans;//正向弧减增广流量 edge[i ^ 1].w += ans;//反向弧加增广流量 del -= ans;//总流量减增广流量 if(del == 0) break;//总流量为0则不继续增广 } } return flow - del;//返回本次增广的流量 } inline ll Dinic() { ll ans = 0; while(Bfs()) { for(int i = 0; i <= node_num; ++i) cur[i] = head[i]; ans += dfs(S, inf); } return ans; } int dic[4][2] = {1,0, -1,0, 0,-1, 0,1}; /* 2 4 3 3 4 1 3 2 3 3 3 1 3 1 2 2 3 */ int main() { int Ti; scanf("%d", &Ti); while (Ti--) { scanf("%d %d", &n, &m); S = 0, T = 2*n+1; node_num = T; init(); int sum = 0; for (int i = 1; i <= n; ++ i) Add_Edge(S, i, 1), Add_Edge(i+n, T, 1); for (int i = 1, u, v; i <= m; ++ i) { scanf("%d %d", &u, &v); Add_Edge(u, v+n, 1); } cout << n - Dinic() << endl; } return 0; }
The K-League#
题意:网络流公平分配模型。告诉你有几支队伍,告诉你已知胜场,以及剩下需要进行的场数。问你哪些队伍可能获得胜利。
附上一个写的很好的博客:网络流和棒球赛淘汰问题 公平分配模板 足球联赛
AC代码:

#pragma GCC optimize(2) #include <bits/stdc++.h> using namespace std; #define pii pair<long long, int> typedef long long ll; const int maxn = 1000 + 10; const int maxm = 1e6 * 2 + 10; const ll inf = 1e14; const int mod = 1e9 + 7; struct Edge{ int to; int next; ll w; } edge[maxm]; int head[maxn], node_num; int cur[maxn];//当前弧优化数组 int n, m, S, T, k;//点数,边数,源点,汇点 int dis[maxn];//Bfs深度 int cnt = 0;//边数 bool vis[maxn]; int w[maxn], d[maxn]; int a[maxn][maxn]; inline void init(){ cnt = 0; for (int i = 0; i <= node_num; ++ i) head[i] = -1; } inline void Add_Edge(int u, int v, ll w){ edge[cnt].next = head[u]; edge[cnt].to = v; edge[cnt].w = w; head[u] = cnt++; edge[cnt].next = head[v]; edge[cnt].to = u; edge[cnt].w = 0; head[v] = cnt++; } inline bool Bfs(){ for(int i = 0; i <= node_num; ++i) dis[i] = -1; //这里要根据你所建的点数来确定 dis[S] = 0; queue<int> q; q.push(S); 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(dis[v] == -1 && edge[i].w){//没有标记深度并且有残量 dis[v] = dis[u] + 1; q.push(v); } } } return dis[T] != -1; } inline ll dfs(int u, ll flow){ if(u == T) return flow; ll del = flow; for(int i = cur[u]; i != -1; i = edge[i].next){ cur[u] = edge[i].next;//当前弧优化,下次直接从cur[u]开始增广,节省时间 int v = edge[i].to; if (dis[v] == dis[u] + 1 && edge[i].w > 0){//深度+1且残量大于0 ll ans = dfs(v, min(del, edge[i].w));//木桶原理 edge[i].w -= ans;//正向弧减增广流量 edge[i ^ 1].w += ans;//反向弧加增广流量 del -= ans;//总流量减增广流量 if(del == 0) break;//总流量为0则不继续增广 } } return flow - del;//返回本次增广的流量 } inline ll Dinic() { ll ans = 0; while(Bfs()) { for(int i = 0; i <= node_num; ++i) cur[i] = head[i]; ans += dfs(S, inf); } return ans; } bool canWin(int team) { //total代表的是team的最大胜场 int total = w[team]; for (int i = 0; i < n; ++ i) total += a[team][i]; //如果存在其他队的胜场已经超过team的最大胜场,那么直接返回 for (int i = 0; i < n; ++ i) if (w[i] > total) return 0; S = 0, T = n*n+n+1; node_num = T; init(); int maxflow = 0; for (int u = 0; u < n; ++ u) { for (int v = u+1; v < n; ++ v) { if (a[u][v] > 0) Add_Edge(S, u*n+v+1, a[u][v]); maxflow += a[u][v]; Add_Edge(u*n+v+1, n*n+u+1, inf); Add_Edge(u*n+v+1, n*n+v+1, inf); } if (w[u] < total) Add_Edge(n*n+u+1, T, total-w[u]); } return Dinic() == maxflow; } /* 3 3 2 0 1 1 0 2 0 2 2 2 0 2 2 2 0 3 4 0 2 2 0 4 0 1 1 1 0 1 1 1 0 4 0 3 3 1 1 3 3 0 0 0 0 2 0 0 1 0 0 1 0 0 2 0 0 0 */ int main() { int T; scanf("%d", &T); while(T--) { scanf("%d", &n); for(int i = 0; i < n; i ++) scanf("%d%d", &w[i], &d[i]); for(int i = 0; i < n; i ++) for(int j = 0; j < n; j++) scanf("%d", &a[i][j]); bool first = true; for(int i = 0; i < n; i++) if(canWin(i)) { if(first) first = false; else printf(" "); printf("%d", i+1); } printf("\n"); } return 0; }
Jamie's Contact Groups#
这道题就是POJ的2289之前用二分图写过了。二分+网络流check就可以了。
我记得,这道题目的读取方式非常地让人难受。

#include<iostream> #include<cstdio> #include<cstring> #include<queue> #include<vector> #include<sstream> using namespace std; const int maxn=2010; const int maxm=1e6+7; const int inf=0x3f3f3f3f; #define pb push_back struct Node { int to; int capa; int next; }edge[maxm]; int n,m; int source,sink; int cnt; int head[maxn]; bool vis[maxn]; int dep[maxn]; char name[100010]; vector<int> vec[maxn]; void init() { memset(head,-1,sizeof(head)); cnt=0; return; } void add(int u,int v,int capa) { edge[cnt].to=v; edge[cnt].capa=capa; edge[cnt].next=head[u]; head[u]=cnt++; edge[cnt].to=u; edge[cnt].capa=0; edge[cnt].next=head[v]; head[v]=cnt++; return; } bool bfs() { queue<int> que; que.push(source); memset(dep,-1,sizeof(dep)); dep[source]=0; while(!que.empty()) { int node=que.front(); que.pop(); for(int i=head[node];~i;i=edge[i].next) { int v=edge[i].to; if(edge[i].capa>0&&dep[v]==-1) { dep[v]=dep[node]+1; if(v==sink) return true; que.push(v); } } } return dep[sink]!=-1; } int dfs(int node,int minn) { if(node==sink||minn==0) { return minn; } int r=0; for(int i=head[node];~i;i=edge[i].next) { int v=edge[i].to; if(dep[v]==dep[node]+1&&edge[i].capa>0) { int tmp=dfs(v,min(edge[i].capa,minn)); if(tmp>0) { edge[i].capa-=tmp; edge[i^1].capa+=tmp; r+=tmp; minn-=tmp; if(!minn) break; } } } if(!r) dep[node]=-1; return r; } int dinic() { int maxflow=0; while(bfs()) { maxflow+=dfs(source,inf); } return maxflow; } void build(int mid) { for(int i=1;i<=n;i++) { add(source,i,1); int len=vec[i].size(); for(int j=0;j<len;j++) { int x=vec[i][j]; add(i,n+x+1,1); } } for(int i=1;i<=m;i++) { add(n+i,sink,mid); } return; } int main() { //freopen("in.txt","r",stdin); //freopen("out.txt","w",stdout); while(~scanf("%d%d",&n,&m)) { getchar(); if(!n&&!m) break; source=0; sink=n+m+10; string str; int num; for(int i=1;i<=n;i++) { vec[i].clear(); getline(cin,str); stringstream stream(str); stream>>name; while(stream>>num) { vec[i].pb(num); } } int left=0; int right=n; while(left<=right) { int mid=(left+right)/2; init(); build(mid); int ans=dinic(); if(ans==n) { right=mid-1; } else { left=mid+1; } } printf("%d\n",left); } return 0; }
Pool construction#
这是一个最小割的很典型的题目。
每个洞要么是草地,要么是洞,我们假设草地是S集合,洞是Y集合,然后洞和草之间要建栅栏,也就可以理解为,用最少的花费将S集合和Y集合分开,这就是最小割的模型了。
初始时,从s点向所有的草地点连一条边,容量为d,割这些边意味着将这个草地变成洞。把每个洞的点向t连一条边,容量为f,割这些边意味着将这个洞变成草。
相邻的两个格子之间u和v,连两条边,u到v和 v到u,容量为b。割u到v这条边意味着u是草,v是洞,在之间建围栏。割v到u的点也类似。
题目还有一个要求,矩阵第一行/列和最后一行/列必须是草,所以,s向这些草连边容量为INF,代表这些边不能割。

#include <cstdio> #include <cstring> #include <algorithm> #include <iostream> #include <queue> using namespace std; const int maxn=3000+10; const int maxw=55; const int maxm=30000; const int INF=2147000000; struct Dinic{ int head[maxn],Next[maxm],to[maxm],cap[maxm],flow[maxm]; int sz,n,m,s,t; bool vis[maxn]; int cur[maxn],d[maxn]; void init(int n){ this->n=n; memset(head,-1,sizeof(head)); sz=-1; } void add_edge(int a,int b,int c){ ++sz; to[sz]=b; cap[sz]=c;flow[sz]=0; Next[sz]=head[a];head[a]=sz; ++sz; to[sz]=a; cap[sz]=c;flow[sz]=c; Next[sz]=head[b];head[b]=sz; } bool BFS(){ memset(vis,0,sizeof(vis)); queue<int>Q; vis[s]=1; d[s]=0; Q.push(s); while(!Q.empty()){ int u=Q.front();Q.pop(); for(int i=head[u];i!=-1;i=Next[i]){ int v=to[i]; if(!vis[v]&&cap[i]>flow[i]){ vis[v]=1; d[v]=d[u]+1; Q.push(v); } } } return vis[t]; } int DFS(int x,int a){ if(x==t||a==0)return a; int Flow=0,f; for(int& i=cur[x];i!=-1;i=Next[i]){ int v=to[i]; if(d[v]==d[x]+1&&(f=DFS(v,min(a,cap[i]-flow[i])))>0){ Flow+=f; flow[i]+=f; flow[i^1]-=f; a-=f; if(a==0)break; } } return Flow; } int Maxflow(int s,int t){ this->s=s,this->t=t; int Flow=0; while(BFS()){ for(int i=0;i<=n;i++) cur[i]=head[i]; Flow+=DFS(s,INF); } return Flow; } }dinic; int w,h,d,f,b,T,ans; char G[maxw][maxw]; int main(){ scanf("%d",&T); for(int t=1;t<=T;t++){ ans=0; scanf("%d%d",&w,&h); scanf("%d%d%d",&d,&f,&b); for(int i=1;i<=h;i++){ for(int j=1;j<=w;j++){ scanf(" %c",&G[i][j]); if(i==1||j==1||i==h||j==w){ if(G[i][j]=='.'){ G[i][j]='#'; ans+=f; } } } } dinic.init(w*h+2); for(int i=1;i<=h;i++){ for(int j=1;j<=w;j++){ if(i==1||j==1||i==h||j==w){ dinic.add_edge(0,(i-1)*w+j,INF); }else{ if(G[i][j]=='#'){ dinic.add_edge(0,(i-1)*w+j,d); } if(G[i][j]=='.'){ dinic.add_edge((i-1)*w+j,h*w+1,f); } } if(i+1<=h){ dinic.add_edge((i-1)*w+j,i*w+j,b); dinic.add_edge(i*w+j,(i-1)*w+j,b); } if(j+1<=w){ dinic.add_edge((i-1)*w+j,(i-1)*w+j+1,b); dinic.add_edge((i-1)*w+j+1,(i-1)*w+j,b); } } } ans+=dinic.Maxflow(0,w*h+1); printf("%d\n",ans); } return 0; }
Cable TV Network#
这题正好学了一下图论中点连通度和边连通度的概念。
1. 基本概念
(1)一个具有 N 个顶点的图,在去掉任意 K-1 个顶点后 (1<=K<=N) 所得的子图仍连通,则称 G 是连通的,
而去掉 K 个顶点后的图不连通, 那么K 称作图 G 的点连通度
(2)相应地如果至少去掉 K 条边使这个图不连通,则 K 成为图的边连通度
2. 求解思路
- 对于求解边联通度的问题,为每条边赋权值为1,然后求确定一点作为源点,枚举此点外的每个点作为汇点求最大流。
- 点联通度问题可以转换到边联通度问题上来,具体转换方法如下
-
若 G 为无向图,假设有n个点:
(1) 原 G 图中的每个顶点 v 变成两个顶点 v' 和 v+n ,顶点 v 至 v+n 有一条弧(有向边)连接,弧容量为 1;
(2) 原 G 图中的每条边 e = uv ,连一条 u+n 到 v 的弧,再连一条 v+n 到 u 的弧,容量均为INF
(3) A” 为源顶点, B' 为汇顶点
注意:弧是有向边
-
若 G 为有向图,假设有n个点:
(1) 原 G 图中的每个顶点 v 变成两个顶点 v' 和 v+n ,顶点 v 至 v+n 有一条弧(有向边)连接,弧容量为 1;
(2) 原 G 图中的每条弧 e = uv 变成一条有向轨 u'u"v'v" ,其中轨上的弧 u"v' 的容量为 ∞;
(3) A” 为源顶点, B' 为汇顶点
-
- 枚举源点 A" ,枚举汇点B',求 A" 到 B' 的最小最大流 F,注意源点汇点的拆点容量应该为inf(为不可割的点)
BTW, 这题网上有比较多的做法是只枚举汇点的,但是按理来说应该是源汇都要枚举。
还有些做法虽然枚举了源汇,但是确实以出点作为源点,入点作为汇点。这样是通过网络流特性来使得源汇不可割,但从理解上还是我这种建图方式较好理解。

#pragma GCC optimize(2) #include <bits/stdc++.h> using namespace std; #define pii pair<long long, int> typedef long long ll; const int maxn = 1000 + 10; const int maxm = 1e6 * 2 + 10; const ll inf = 1e14; const int mod = 1e9 + 7; struct Edge{ int to; int next; ll w; } edge[maxm]; int head[maxn], node_num; int cur[maxn];//当前弧优化数组 int n, m, S, T, k;//点数,边数,源点,汇点 int dis[maxn];//Bfs深度 int cnt = 0;//边数 bool vis[maxn]; int w[maxn], d[maxn]; int a[maxn][maxn]; inline void init(){ cnt = 0; for (int i = 0; i <= node_num; ++ i) head[i] = -1; } inline void Add_Edge(int u, int v, ll w){ edge[cnt].next = head[u]; edge[cnt].to = v; edge[cnt].w = w; head[u] = cnt++; edge[cnt].next = head[v]; edge[cnt].to = u; edge[cnt].w = 0; head[v] = cnt++; } inline bool Bfs(){ for(int i = 0; i <= node_num; ++i) dis[i] = -1; //这里要根据你所建的点数来确定 dis[S] = 0; queue<int> q; q.push(S); 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(dis[v] == -1 && edge[i].w){//没有标记深度并且有残量 dis[v] = dis[u] + 1; q.push(v); } } } return dis[T] != -1; } inline ll dfs(int u, ll flow){ if(u == T) return flow; ll del = flow; for(int i = cur[u]; i != -1; i = edge[i].next){ cur[u] = edge[i].next;//当前弧优化,下次直接从cur[u]开始增广,节省时间 int v = edge[i].to; if (dis[v] == dis[u] + 1 && edge[i].w > 0){//深度+1且残量大于0 ll ans = dfs(v, min(del, edge[i].w));//木桶原理 edge[i].w -= ans;//正向弧减增广流量 edge[i ^ 1].w += ans;//反向弧加增广流量 del -= ans;//总流量减增广流量 if(del == 0) break;//总流量为0则不继续增广 } } return flow - del;//返回本次增广的流量 } inline ll Dinic() { ll ans = 0; while(Bfs()) { for(int i = 0; i <= node_num; ++i) cur[i] = head[i]; ans += dfs(S, inf); } return ans; } /* 0 0 1 0 3 3 (0,1) (0,2) (1,2) 2 0 5 7 (0,1) (0,2) (1,3) (1,2) (1,4) (2,3) (3,4) */ int main(){ while (~scanf("%d%d", &n, &m)){ int u[m+1], v[m+1]; for (int i = 0; i < m; i++) scanf(" (%d,%d)", &u[i], &v[i]);//, u[i]++, v[i]++; ll ans = inf; node_num = 2*n; for(int s=0;s<n;s++){ for(int t=s+1;t<n;t++){ init(); S = s, T = t; for(int i=0;i<n;i++) { if (i == s || i == t) Add_Edge(i,i+n,inf); else Add_Edge(i,i+n,1); } for(int i=0;i<m;i++) Add_Edge(u[i]+n,v[i],inf),Add_Edge(v[i]+n,u[i],inf); ll temp = Dinic(); ans = min(ans,temp); } } if(ans == inf){ if(n*(n-1)/2==m) printf("%d\n",n); else printf("0\n"); } else printf("%d\n",ans); } return 0; }
Sabotage#
求最小割边集。其实我觉得求出残余网络中容量为0的就好了,不就是最小割么。(不知道对不对)
(UPD20.9.11)跑满流的边只是可能为最小割边,反例: 1 ----(12)------2-------(12)--------3 假设S为1,T为3。如果满流的边是最小割的话,那么就是1-2和2-3,但是答案其实是1-2或者1-3。所以下述方法才是求最小割的正解!
但是网上的做法好像都是先最大流之后dfs染色,染色中遇到容量为0的跳过。
之后判断每条边左右节点的颜色是否不同就好了。

#pragma GCC optimize(2) #include <bits/stdc++.h> using namespace std; #define pii pair<long long, int> typedef long long ll; const int maxn = 1000 + 10; const int maxm = 1e6 * 2 + 10; const ll inf = 1e14; const int mod = 1e9 + 7; struct Edge{ int to; int next; ll w; } edge[maxm]; int head[maxn], node_num; int cur[maxn];//当前弧优化数组 int n, m, S, T, k;//点数,边数,源点,汇点 int dis[maxn];//Bfs深度 int cnt = 0;//边数 bool vis[maxn]; inline void init(){ cnt = 0; for (int i = 0; i <= node_num; ++ i) head[i] = -1; memset(vis, 0, sizeof(vis)); } inline void Add_Edge(int u, int v, ll w){ edge[cnt].next = head[u]; edge[cnt].to = v; edge[cnt].w = w; head[u] = cnt++; edge[cnt].next = head[v]; edge[cnt].to = u; edge[cnt].w = 0; head[v] = cnt++; } inline bool Bfs(){ for(int i = 0; i <= node_num; ++i) dis[i] = -1; //这里要根据你所建的点数来确定 dis[S] = 0; queue<int> q; q.push(S); 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(dis[v] == -1 && edge[i].w){//没有标记深度并且有残量 dis[v] = dis[u] + 1; q.push(v); } } } return dis[T] != -1; } inline ll dfs(int u, ll flow){ if(u == T) return flow; ll del = flow; for(int i = cur[u]; i != -1; i = edge[i].next){ cur[u] = edge[i].next;//当前弧优化,下次直接从cur[u]开始增广,节省时间 int v = edge[i].to; if (dis[v] == dis[u] + 1 && edge[i].w > 0){//深度+1且残量大于0 ll ans = dfs(v, min(del, edge[i].w));//木桶原理 edge[i].w -= ans;//正向弧减增广流量 edge[i ^ 1].w += ans;//反向弧加增广流量 del -= ans;//总流量减增广流量 if(del == 0) break;//总流量为0则不继续增广 } } return flow - del;//返回本次增广的流量 } inline ll Dinic() { ll ans = 0; while(Bfs()) { for(int i = 0; i <= node_num; ++i) cur[i] = head[i]; ans += dfs(S, inf); } return ans; } void DFS(int u) { vis[u] = 1;//为1表示是S集的点 for (int i = head[u]; ~i; i = edge[i].next) { int v = edge[i].to; if (vis[v] || !edge[i].w) continue; DFS(v); } } int main() { while (~scanf("%d%d",&n,&m)) { if (!n && !m) break; node_num = n; S = 1, T = 2; init(); int x[m+1], y[m+1]; for (int i = 1; i <= m; ++ i) { int u, v, w; scanf("%d%d%d",&u,&v,&w); Add_Edge(u, v, w), Add_Edge(v, u, w); x[i] = u, y[i] = v; } Dinic(); DFS(S); for(int i = 1; i <= m; i++) { int u = x[i], v = y[i]; if (vis[u] && !vis[v] || vis[v] && !vis[u]) printf("%d %d\n", u, v); } cout << endl; } return 0; }
Collectors Problem#
这道题我好像见过。这也算是一类题了吧(分配问题)。
题目大意:Bob有一些贴纸,他可以和别人交换,他可以把自己独有的贴纸拿出去,也可以把重复的贴纸拿出去(有时候把独有的贴纸而不是重复的贴纸拿出去能换到更多贴纸)。
Bob的朋友也有一些贴纸,但是他们只会拿自己重复的贴纸和Bob换,而且换的是自己没有的贴纸。
求Bob最后最多能有多少种贴纸。
解题思路:
题目意思很明确了。就算把重复的贴纸拿出去也不一定最优,贪心就不用尝试了。
全局资源调配得使用网络流模型。
建图方式:
①S点(看作是Bob)->所有物品:连一条边,cap是Bob持有贴纸数量。
②:所有朋友->所有物品:如果这个人持有的该贴纸数量>=2,连一条边,cap是贴纸数量-1。(原因是这些人只会把重复的贴纸拿出去)。
③:所有物品->所有朋友:如果这个人没有改物品,连一条边,cap=1,。(原因是这些人会接受自己没有的贴纸)
④:所有物品->T点:连一条边,cap=1,统计物品的种类。
这样建图之后,所有物品可以看作Bob的总资产,这个总资产可以流进,也可以流出,在这基础上做一次最大流,就是结果了。

#pragma GCC optimize(2) #include <bits/stdc++.h> using namespace std; #define pii pair<long long, int> typedef long long ll; const int maxn = 1000 + 10; const int maxm = 1e6 * 2 + 10; const ll inf = 1e14; const int mod = 1e9 + 7; struct Edge{ int to; int next; ll w; } edge[maxm]; int head[maxn], node_num; int cur[maxn];//当前弧优化数组 int n, m, S, T, k;//点数,边数,源点,汇点 int dis[maxn];//Bfs深度 int tot = 0;//边数 bool vis[maxn]; inline void init(){ tot = 0; for (int i = 0; i <= node_num; ++ i) head[i] = -1; memset(vis, 0, sizeof(vis)); } inline void Add_Edge(int u, int v, ll w){ edge[tot].next = head[u]; edge[tot].to = v; edge[tot].w = w; head[u] = tot++; edge[tot].next = head[v]; edge[tot].to = u; edge[tot].w = 0; head[v] = tot++; } inline bool Bfs(){ for(int i = 0; i <= node_num; ++i) dis[i] = -1; //这里要根据你所建的点数来确定 dis[S] = 0; queue<int> q; q.push(S); 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(dis[v] == -1 && edge[i].w){//没有标记深度并且有残量 dis[v] = dis[u] + 1; q.push(v); } } } return dis[T] != -1; } inline ll dfs(int u, ll flow){ if(u == T) return flow; ll del = flow; for(int i = cur[u]; i != -1; i = edge[i].next){ cur[u] = edge[i].next;//当前弧优化,下次直接从cur[u]开始增广,节省时间 int v = edge[i].to; if (dis[v] == dis[u] + 1 && edge[i].w > 0){//深度+1且残量大于0 ll ans = dfs(v, min(del, edge[i].w));//木桶原理 edge[i].w -= ans;//正向弧减增广流量 edge[i ^ 1].w += ans;//反向弧加增广流量 del -= ans;//总流量减增广流量 if(del == 0) break;//总流量为0则不继续增广 } } return flow - del;//返回本次增广的流量 } inline ll Dinic() { ll ans = 0; while(Bfs()) { for(int i = 0; i <= node_num; ++i) cur[i] = head[i]; ans += dfs(S, inf); } return ans; } void DFS(int u) { vis[u] = 1;//为1表示是S集的点 for (int i = head[u]; ~i; i = edge[i].next) { int v = edge[i].to; if (vis[v] || !edge[i].w) continue; DFS(v); } } /* 2 2 5 6 1 1 1 1 1 1 3 1 2 2 3 5 4 1 2 1 1 3 2 2 2 5 1 3 4 4 3 */ int main() { int Ti; scanf("%d", &Ti); for (int Tii = 1; Tii <= Ti; ++ Tii) { scanf("%d %d", &n, &m); node_num = n+m+1; S = 0, T = n+m+1; init(); int cnt[n+1][m+1] = {0}; for (int u = 1, v; u <= n; u++) { int num; scanf("%d", &num); while (num--) { scanf("%d", &v); cnt[u][v]++; } } //1号是自己 for (int i = 1; i <= m; i++) Add_Edge(S, i, cnt[1][i]); //2-n是其他人 for (int i = 2; i <= n; i++) { for (int j = 1; j <= m; j++) { if (!cnt[i][j]) Add_Edge(j, m+i, 1); else Add_Edge(m+i, j, cnt[i][j]-1); } } for (int i = 1; i <= m; i++) Add_Edge(i, T, 1); int ans = Dinic(); printf("Case #%d: %d\n", Tii, ans); } return 0; }
Matrix Decompressing#
思路:
这么也想不到用网络流解决,这个模型很不错。假设这个矩阵的每一行是水管,每一列是水管,每行有出水口流到每一列。
这样想比较好理解。然后每行的流量和每列的流量知道,就可以建图了。
建图过程,每行对应一个点,每列对应1个点,每行都可以流到每列,所以他们之间有边。
我们得假设他们是如何流向的,不如设从行流向列,那么添加源点,流向每行;添加汇点,被每列汇流。容量怎么设?我们要让每行都满流就行了,
那么行之和就是源点到该行的容量,同理汇点也如此。但是每行流向每列呢?注意每个格子的元素必须在1~20之间,所以把容量设为20,别让它流太多了。
注意到元素必须在1~20之间!!!那么这样增广路的话会出现有的完全0流怎么办?先将每个格子中的元素自减1,它的流下限总不会为负吧,计算完输出时再加回去不就行了。

#pragma GCC optimize(2) #include <bits/stdc++.h> using namespace std; #define pii pair<long long, int> typedef long long ll; const int maxn = 1e6 + 10; const int maxm = 1e5 * 2 + 10; const ll inf = 1e18; const int mod = 1e9 + 7; struct Edge{ int to; int next; ll w; } edge[maxn*2]; int head[maxn], node_num; int cur[maxn];//当前弧优化数组 int n, m, S, T, k;//点数,边数,源点,汇点 int dis[maxn];//Bfs深度 int cnt = 0;//边数 bool vis[maxn]; inline void init(){ cnt = 0; for (int i = 0; i <= node_num; ++ i) head[i] = -1; } inline void Add_Edge(int u, int v, ll w){ edge[cnt].next = head[u]; edge[cnt].to = v; edge[cnt].w = w; head[u] = cnt++; edge[cnt].next = head[v]; edge[cnt].to = u; edge[cnt].w = 0; head[v] = cnt++; } inline bool Bfs(){ for(int i = 0; i <= node_num; ++i) dis[i] = -1; //这里要根据你所建的点数来确定 dis[S] = 0; queue<int> q; q.push(S); while(!q.empty()){ int u = q.front(); q.pop(); for(int i = head[u]; ~i ; i = edge[i].next){ int v = edge[i].to; if(dis[v] == -1 && edge[i].w){//没有标记深度并且有残量 dis[v] = dis[u] + 1; q.push(v); } } } return dis[T] != -1; } inline ll dfs(int u, ll flow) { if(u == T) return flow; ll del = flow; for(int i = cur[u]; ~i; i = edge[i].next){ cur[u] = edge[i].next;//当前弧优化,下次直接从cur[u]开始增广,节省时间 int v = edge[i].to; if (dis[v] == dis[u] + 1 && edge[i].w > 0){//深度+1且残量大于0 ll ans = dfs(v, min(del, edge[i].w));//木桶原理 edge[i].w -= ans;//正向弧减增广流量 edge[i ^ 1].w += ans;//反向弧加增广流量 del -= ans;//总流量减增广流量 if(del == 0) break;//总流量为0则不继续增广 } } return flow - del;//返回本次增广的流量 } inline ll Dinic() { ll ans = 0; while(Bfs()) { for(int i = 0; i <= node_num; ++i) cur[i] = head[i]; ans += dfs(S, inf); } return ans; } int main() { //freopen("in.txt","r",stdin); int Ti; scanf("%d",&Ti); for (int ti = 1; ti <= Ti; ++ ti) { init(); cin>>n>>m; S = 0, T = 1+n+m; node_num = T; int a[30]={0}, b[30]={0}, c[30]={0}, d[30]={0}; int rec[30][30] = {0}; for (int i=1; i<=n; i++) cin>>a[i]; for (int i=1; i<=m; i++) cin>>b[i]; for (int i=1; i<=n; i++) c[i] = a[i]-a[i-1]-m; for (int i=1; i<=m; i++) d[i] = b[i]-b[i-1]-n; for (int i=1; i<=n; i++) Add_Edge(S,i,c[i]); for (int i=1; i<=m; i++) Add_Edge(n+i,T,d[i]); for (int i=1; i<=n; i++) for (int j=1; j<=m; j++) Add_Edge(i,n+j,19), rec[i][j] = cnt-1; Dinic(); cout << "Matrix " << ti << endl; for (int i = 1; i <= n; ++ i) { int j; for (j = 1; j <= m-1; ++ j) { cout << edge[rec[i][j]].w + 1 << " "; } cout << edge[rec[i][m]].w +1<< endl;//edge[j].w+1 << endl; } cout << endl; } return 0; }
Frequency Hopping#
这道题真的很好。为了做这道题我特地去重新学习了一遍网络流。
题意:问你是否在网络流中存在流量为C的流,如果不存在是否能修改一条边使得存在。
思路:存在流量为C那么只需要最大流>=C就好了,因为最大流可以不跑满。
所以possible的条件就是最大流.>= C
通过最大流最小割定理我们知道,当流量是最大流的时候,割边流量肯定跑满,所以修改容量的边肯定是割边。
所以我们枚举割边去修改
(暴力会超时。所以这里要提两点优化:
第一点是当跑最大流的时候一旦流量>=C那么直接跳出。
第二点是从跑了一遍最大流的残余网络上再跑最大流。
第一个点是比较好理解的就不讲了。第二点是因为Dinic算法是“阻塞流”,所以跑了一边dinic之后他会把一些道路给阻塞,就会使得增广的路减少从而达到优化时间的目的。
)
如果每一条割边都不能满足就是not possible
(最让我难受的是我的板子似乎不能满足这道题,改了一个上午还是超时。最后还是嫖了白书上的板子,以防日后会遇到相同的题目,用自己的板子写不出来。)

#include<cstdio> #include<cstring> #include<algorithm> #include<vector> #include<queue> using namespace std; #define maxn 110 #define maxm 22010 typedef long long ll; #define INF 0x3f3f3f3f #define cl(x,v); memset(x,v,sizeof(x)); struct Edge{ int from,to,cap,flow; }; bool cmp(const Edge& a,const Edge& b) { return a.from < b.from||(a.from==b.from&&a.to<b.to); } struct Dinic{ int n,m,s,t; vector<Edge>edges; vector<int>g[maxn]; bool vis[maxn]; int d[maxn]; int cur[maxm]; void init(int n) { this->n=n; for(int i=0;i<=n;i++)g[i].clear(); edges.clear(); } void AddEdge(int from,int to,int cap) { edges.push_back((Edge){from,to,cap,0}); edges.push_back((Edge){to,from,0,0}); m=edges.size(); g[from].push_back(m-2); g[to].push_back(m-1); } bool BFS() { cl(vis,0); queue<int>q; q.push(s); d[s]=0; vis[s]=1; while(!q.empty()) { int x=q.front(); q.pop(); for(int i=0;i<g[x].size();i++) { Edge& e=edges[g[x][i]]; if(!vis[e.to]&&e.cap>e.flow) { vis[e.to]=1; d[e.to]=d[x]+1; q.push(e.to); } } } return vis[t]; } int DFS(int x,int a) { if(x==t||a==0)return a; int flow=0,f; for(int& i=cur[x];i<g[x].size();i++) { Edge& e=edges[g[x][i]]; if(d[x]+1==d[e.to]&&(f=DFS(e.to,min(a,e.cap-e.flow)))>0) { e.flow+=f; edges[g[x][i]^1].flow-=f; flow+=f; a-=f; if(a==0)break; } } return flow; } int Maxflow(int s,int t,int need) { this->s=s; this->t=t; int flow=0; while(BFS()) { cl(cur,0); flow+=DFS(s,INF); if(flow>need)return flow; } return flow; } vector<int> Mincut() { BFS(); vector<int>ans; for(int i=0;i<edges.size();i++) { Edge& e=edges[i]; if(vis[e.from]&&!vis[e.to]&&e.cap>0) ans.push_back(i); } return ans; } void Reduce() { for(int i=0;i<edges.size();i++) edges[i].cap-=edges[i].flow; } void ClearFlow() { for(int i=0;i<edges.size();i++) edges[i].flow=0; } }; Dinic solver; int main() { int N,E,C,cas=0; while(scanf("%d%d%d",&N,&E,&C)!=EOF) { if(N==0)break; solver.init(N); int a,b,c; while(E--) { scanf("%d%d%d",&a,&b,&c); solver.AddEdge(a,b,c); } int flow=solver.Maxflow(1,N,INF); printf("Case %d: ",++cas); if(flow>=C)printf("possible\n"); else { vector<int>cut=solver.Mincut(); solver.Reduce(); vector<Edge>ans; for(int i=0;i<cut.size();i++) { Edge& e=solver.edges[cut[i]]; int temp=e.cap; e.cap=C; solver.ClearFlow(); if(flow+solver.Maxflow(1,N,C-flow)>=C)ans.push_back(e); e.cap=temp; } if(ans.empty())printf("not possible\n"); else { sort(ans.begin(),ans.end(),cmp); printf("possible option:(%d,%d)",ans[0].from,ans[0].to); for(int i=1;i<ans.size();i++) printf(",(%d,%d)",ans[i].from,ans[i].to); printf("\n"); } } } return 0; }
SAM I AM#
这道题跟上面那道挺像的。这道题让我解决了上面遗留的疑惑。同时也让我对最小割理解更为深刻。

#pragma GCC optimize(2) #include <bits/stdc++.h> using namespace std; #define pii pair<long long, int> typedef long long ll; const int maxn = 3000 + 10; const int maxm = 1e6 * 2 + 100; const ll inf = 1e14; const int mod = 1e9 + 7; struct Edge{ int to; int next; ll w; } edge[maxm]; int head[maxn], node_num; int cur[maxn];//当前弧优化数组 int n, m, S, T, num;//点数,边数,源点,汇点 int dis[maxn];//Bfs深度 int cnt = 0;//边数 bool vis[maxn]; inline void init(){ cnt = 0; for (int i = 0; i <= node_num; ++ i) head[i] = -1; memset(vis, 0, sizeof(vis)); } inline void Add_Edge(int u, int v, ll w){ edge[cnt].next = head[u]; edge[cnt].to = v; edge[cnt].w = w; head[u] = cnt++; edge[cnt].next = head[v]; edge[cnt].to = u; edge[cnt].w = 0; head[v] = cnt++; } inline bool Bfs(){ for(int i = 0; i <= node_num; ++i) dis[i] = -1; //这里要根据你所建的点数来确定 dis[S] = 0; queue<int> q; q.push(S); 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(dis[v] == -1 && edge[i].w){//没有标记深度并且有残量 dis[v] = dis[u] + 1; q.push(v); } } } return dis[T] != -1; } inline ll dfs(int u, ll flow){ if(u == T) return flow; ll del = flow; for(int i = cur[u]; i != -1; i = edge[i].next){ cur[u] = edge[i].next;//当前弧优化,下次直接从cur[u]开始增广,节省时间 int v = edge[i].to; if (dis[v] == dis[u] + 1 && edge[i].w > 0){//深度+1且残量大于0 ll ans = dfs(v, min(del, edge[i].w));//木桶原理 edge[i].w -= ans;//正向弧减增广流量 edge[i ^ 1].w += ans;//反向弧加增广流量 del -= ans;//总流量减增广流量 if(del == 0) break;//总流量为0则不继续增广 } } return flow - del;//返回本次增广的流量 } inline ll Dinic() { ll ans = 0; while(Bfs()) { for(int i = 0; i <= node_num; ++i) cur[i] = head[i]; ans += dfs(S, inf); } return ans; } void DFS(int u) { vis[u] = 1;//为1表示是S集的点 for (int i = head[u]; ~i; i = edge[i].next) { int v = edge[i].to; if (vis[v] || !edge[i].w) continue; DFS(v); } } int main() { while (~scanf("%d%d%d",&n,&m,&num)) { if (!n && !m && !num) break; node_num = n+m+1; S = 0, T = m+n+1; init(); for (int i = 1; i <= n; ++ i) Add_Edge(S, i, 1); for (int i = 1; i <= m; ++ i) Add_Edge(i+n, T, 1); for (int i = 1,u,v; i <= num; ++ i) scanf("%d%d",&u,&v), Add_Edge(u, v+n, 1); printf("%lld ", Dinic()); DFS(S); for (int i = 1; i <= n; ++ i) if (!vis[i]) printf("r%d ", i); for (int i = n+1; i <= m+n; ++ i) if (vis[i]) printf("c%d ", i-n); cout << endl; } return 0; } /* 4 4 3 1 1 1 4 3 2 4 4 2 1 1 2 2 0 0 0 */
Power Transmission#
这道题算是比较综合的题目吧,多源多汇+点容量限制。还行。

#pragma GCC optimize(2) #include <bits/stdc++.h> using namespace std; #define pii pair<long long, int> typedef long long ll; const int maxn = 3000 + 10; const int maxm = 1e6 * 2 + 100; const ll inf = 1e14; const int mod = 1e9 + 7; struct Edge{ int to; int next; ll w; } edge[maxm]; int head[maxn], node_num; int cur[maxn];//当前弧优化数组 int n, m, S, T, num;//点数,边数,源点,汇点 int dis[maxn];//Bfs深度 int cnt = 0;//边数 bool vis[maxn]; inline void init(){ cnt = 0; for (int i = 0; i <= node_num; ++ i) head[i] = -1; memset(vis, 0, sizeof(vis)); } inline void Add_Edge(int u, int v, ll w){ edge[cnt].next = head[u]; edge[cnt].to = v; edge[cnt].w = w; head[u] = cnt++; edge[cnt].next = head[v]; edge[cnt].to = u; edge[cnt].w = 0; head[v] = cnt++; } inline bool Bfs(){ for(int i = 0; i <= node_num; ++i) dis[i] = -1; //这里要根据你所建的点数来确定 dis[S] = 0; queue<int> q; q.push(S); 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(dis[v] == -1 && edge[i].w){//没有标记深度并且有残量 dis[v] = dis[u] + 1; q.push(v); } } } return dis[T] != -1; } inline ll dfs(int u, ll flow){ if(u == T) return flow; ll del = flow; for(int i = cur[u]; i != -1; i = edge[i].next){ cur[u] = edge[i].next;//当前弧优化,下次直接从cur[u]开始增广,节省时间 int v = edge[i].to; if (dis[v] == dis[u] + 1 && edge[i].w > 0){//深度+1且残量大于0 ll ans = dfs(v, min(del, edge[i].w));//木桶原理 edge[i].w -= ans;//正向弧减增广流量 edge[i ^ 1].w += ans;//反向弧加增广流量 del -= ans;//总流量减增广流量 if(del == 0) break;//总流量为0则不继续增广 } } return flow - del;//返回本次增广的流量 } inline ll Dinic() { ll ans = 0; while(Bfs()) { for(int i = 0; i <= node_num; ++i) cur[i] = head[i]; ans += dfs(S, inf); } return ans; } void DFS(int u) { vis[u] = 1;//为1表示是S集的点 for (int i = head[u]; ~i; i = edge[i].next) { int v = edge[i].to; if (vis[v] || !edge[i].w) continue; DFS(v); } } int main() { //freopen("out.txt","w",stdout); while (~scanf("%d",&n)) { node_num = 2*n+1; S = 0, T = 2*n+1; init(); for (int i = 1, w; i <= n; ++ i) scanf("%d", &w), Add_Edge(i, i+n, w); scanf("%d", &m); for (int i = 1,u,v,w; i <= m; ++ i) scanf("%d%d%d",&u, &v, &w), Add_Edge(u+n, v, w); int ed, st; scanf("%d%d", &st, &ed); for (int i = 1, tt; i <= st; ++ i) scanf("%d", &tt), Add_Edge(S, tt, inf); for (int i = 1, tt; i <= ed; ++ i) scanf("%d", &tt), Add_Edge(tt+n, T, inf); printf("%lld\n", Dinic()); } return 0; } /* 4 4 3 1 1 1 4 3 2 4 4 2 1 1 2 2 0 0 0 */
多源多汇拆点。跟上一题差不多。只不过换成了矩阵的形式。

#pragma GCC optimize(2) #include <bits/stdc++.h> using namespace std; #define pii pair<long long, int> typedef long long ll; const int maxn = 3000 + 10; const int maxm = 1e6 * 2 + 100; const ll inf = 1e14; const int mod = 1e9 + 7; struct Edge{ int to; int next; ll w; } edge[maxm]; int head[maxn], node_num; int cur[maxn];//当前弧优化数组 int n, m, S, T, c;//点数,边数,源点,汇点 int dis[maxn];//Bfs深度 int cnt = 0;//边数 bool vis[maxn]; char G[35][35]; inline void init(){ cnt = 0; for (int i = 0; i <= node_num; ++ i) head[i] = -1; memset(vis, 0, sizeof(vis)); } inline void Add_Edge(int u, int v, ll w){ edge[cnt].next = head[u]; edge[cnt].to = v; edge[cnt].w = w; head[u] = cnt++; edge[cnt].next = head[v]; edge[cnt].to = u; edge[cnt].w = 0; head[v] = cnt++; } inline bool Bfs(){ for(int i = 0; i <= node_num; ++i) dis[i] = -1; //这里要根据你所建的点数来确定 dis[S] = 0; queue<int> q; q.push(S); 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(dis[v] == -1 && edge[i].w){//没有标记深度并且有残量 dis[v] = dis[u] + 1; q.push(v); } } } return dis[T] != -1; } inline ll dfs(int u, ll flow){ if(u == T) return flow; ll del = flow; for(int i = cur[u]; i != -1; i = edge[i].next){ cur[u] = edge[i].next;//当前弧优化,下次直接从cur[u]开始增广,节省时间 int v = edge[i].to; if (dis[v] == dis[u] + 1 && edge[i].w > 0){//深度+1且残量大于0 ll ans = dfs(v, min(del, edge[i].w));//木桶原理 edge[i].w -= ans;//正向弧减增广流量 edge[i ^ 1].w += ans;//反向弧加增广流量 del -= ans;//总流量减增广流量 if(del == 0) break;//总流量为0则不继续增广 } } return flow - del;//返回本次增广的流量 } inline ll Dinic() { ll ans = 0; while(Bfs()) { for(int i = 0; i <= node_num; ++i) cur[i] = head[i]; ans += dfs(S, inf); } return ans; } void DFS(int u) { vis[u] = 1;//为1表示是S集的点 for (int i = head[u]; ~i; i = edge[i].next) { int v = edge[i].to; if (vis[v] || !edge[i].w) continue; DFS(v); } } int dic[4][2] ={0,1,0,-1,1,0,-1,0}; int main() { //freopen("out.txt","w",stdout); while (~scanf("%d%d%d",&n,&m,&c)) { node_num = 2*n*m+1; S = 0, T = 2*n*m+1; init(); for (int i = 1, w; i <= n; ++ i) scanf("%s", (G[i]+1)); for (int i = 1; i <= n; ++ i) { for (int j = 1; j <= m; ++ j) { int id = (i-1)*m+j; if (G[i][j] == '*') Add_Edge(S, id, 1), Add_Edge(id, id+n*m, 1); else if (G[i][j] == '~') continue; else if (G[i][j] == '.') Add_Edge(id, id+n*m, 1); else if (G[i][j] == '@') Add_Edge(id, id+n*m, inf); else if (G[i][j] == '#') Add_Edge(id+n*m, T, c), Add_Edge(id, id+n*m, inf); for (int k = 0; k < 4; ++ k) { int tx = i+dic[k][0]; int ty = j+dic[k][1]; int id1 = (tx-1)*m+ty; if (tx < 1 || tx > n || ty < 1 || ty > m || G[tx][ty] == '*' || G[tx][ty] == '~') continue; Add_Edge(id+n*m, id1, inf); } } } printf("%lld\n", Dinic()); } return 0; } /* 4 4 3 1 1 1 4 3 2 4 4 2 1 1 2 2 0 0 0 */
这题在洛谷上居然是黑题。也不是很难的样子。
题意:给定一些航班,每个航班有人数,和起始终止时间,每次转机要花半小时,问限制时间内最多能有多少人从起始城市到终点城市
思路:dinic最大流,将每个班机作为一个点,并且拆点成i'和i'',从i'->i''的边的限制为班机的人数,两个班机i和j如果i的终点是j的起点,且时间间隔至少为30分钟则在i''->j'建立边,最后是源点和汇点的边。

#pragma GCC optimize(2) #include <bits/stdc++.h> using namespace std; #define pii pair<long long, int> typedef long long ll; const int maxn = 1e4 + 10; const int maxm = 1e6 * 2 + 100; const ll inf = 1e14; const int mod = 1e9 + 7; struct Edge{ int to, next; ll w; } edge[maxm]; int head[maxn], node_num; int cur[maxn];//当前弧优化数组 int n, m, S, T, c;//点数,边数,源点,汇点 int dis[maxn];//Bfs深度 int cnt = 0;//边数 //bool vis[maxn]; inline void init(){ cnt = 0; for (int i = 0; i <= node_num; ++ i) head[i] = -1; // memset(vis, 0, sizeof(vis)); } inline void Add_Edge(int u, int v, ll w){ edge[cnt].next = head[u]; edge[cnt].to = v; edge[cnt].w = w; head[u] = cnt++; edge[cnt].next = head[v]; edge[cnt].to = u; edge[cnt].w = 0; head[v] = cnt++; } inline bool Bfs(){ for(int i = 0; i <= node_num; ++i) dis[i] = -1; //这里要根据你所建的点数来确定 dis[S] = 0; queue<int> q; q.push(S); 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(dis[v] == -1 && edge[i].w){//没有标记深度并且有残量 dis[v] = dis[u] + 1; q.push(v); } } } return dis[T] != -1; } inline ll dfs(int u, ll flow){ if(u == T) return flow; ll del = flow; for(int i = cur[u]; i != -1; i = edge[i].next){ cur[u] = edge[i].next;//当前弧优化,下次直接从cur[u]开始增广,节省时间 int v = edge[i].to; if (dis[v] == dis[u] + 1 && edge[i].w > 0){//深度+1且残量大于0 ll ans = dfs(v, min(del, edge[i].w));//木桶原理 edge[i].w -= ans;//正向弧减增广流量 edge[i ^ 1].w += ans;//反向弧加增广流量 del -= ans;//总流量减增广流量 if(del == 0) break;//总流量为0则不继续增广 } } return flow - del;//返回本次增广的流量 } inline ll Dinic() { ll ans = 0; while(Bfs()) { for(int i = 0; i <= node_num; ++i) cur[i] = head[i]; ans += dfs(S, inf); } return ans; } struct Plane { int u, v, num, stt, edt; }P[maxn]; int main() { //freopen("out.txt","w",stdout); ios::sync_with_stdio(0); cin.tie(0); while (cin >> n) { map<string, int> mp; int hash_val = 0; string st, ed; cin >> st >> ed; mp[st] = hash_val++; mp[ed] = hash_val++; int end_time; cin >> end_time; end_time = end_time/100*60 + end_time%100; cin >> m; S = 0, T = 2*m+1; node_num = 2*m+1; init(); for (int i = 1; i <= m; ++ i) { string u, v; int num, edt, stt; cin >> u >> v >> num >> stt >> edt; if (!mp[u]) mp[u] = hash_val++; if (!mp[v]) mp[v] = hash_val++; stt = stt/100*60 + stt%100; edt = edt/100*60 + edt%100; P[i] = {mp[u], mp[v], num, stt, edt}; } for (int i = 1; i <= m; ++ i) { Add_Edge(i, i+m, P[i].num); if (P[i].u == mp[st]) Add_Edge(S, i, inf); if (P[i].v == mp[ed] && P[i].edt <= end_time) Add_Edge(i+m, T, inf); for (int j = 1; j <= m; ++j) { if (i == j) continue; if (P[i].v == P[j].u && P[j].stt - P[i].edt >= 30) Add_Edge(i+m, j, inf); } } printf("%lld\n", Dinic()); } return 0; } /* 4 lisbon berlin 1500 9 lisbon london 6 1000 1100 london lisbon 6 1130 1230 lisbon paris 5 1000 1100 paris lisbon 4 1130 1230 london paris 1 1130 1300 london berlin 2 1340 1510 berlin london 2 1300 1430 paris berlin 10 1330 1500 berlin paris 9 1300 1430 */
Hard Life#
这道题是最大密度子图的模板题。
第一种方式

#pragma GCC optimize(2) #include <bits/stdc++.h> using namespace std; #define pii pair<long long, int> typedef long long ll; const int maxn = 2011; const int maxm = 1e6 * 2 + 100; const double inf = 1e9; const int mod = 1e9 + 7; struct Edge{ int to, next; double w; } edge[maxn*4]; int head[maxn], node_num; int cur[maxn];//当前弧优化数组 int n, m, S, T, c;//点数,边数,源点,汇点 int dis[maxn];//Bfs深度 int cnt = 0;//边数 int vis[maxn]; int tx[maxn], ty[maxn]; inline void Add_Edge(int u, int v, double w){ edge[cnt].next = head[u]; edge[cnt].to = v; edge[cnt].w = w; head[u] = cnt++; edge[cnt].next = head[v]; edge[cnt].to = u; edge[cnt].w = 0; head[v] = cnt++; } inline bool Bfs(){ for(int i = 0; i <= node_num; ++i) dis[i] = -1; dis[S] = 0; queue<int> q; q.push(S); 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(dis[v] == -1 && edge[i].w){//没有标记深度并且有残量 dis[v] = dis[u] + 1; q.push(v); } } } return dis[T] != -1; } inline double dfs(int u, double flow){ if(u == T) return flow; double del = flow; for(int i = cur[u]; i != -1; i = edge[i].next){ cur[u] = edge[i].next;//当前弧优化,下次直接从cur[u]开始增广,节省时间 int v = edge[i].to; if (dis[v] == dis[u] + 1 && edge[i].w > 0){//深度+1且残量大于0 double ans = dfs(v, min(del, edge[i].w));//木桶原理 edge[i].w -= ans;//正向弧减增广流量 edge[i ^ 1].w += ans;//反向弧加增广流量 del -= ans;//总流量减增广流量 if(del == 0) break;//总流量为0则不继续增广 } } return flow - del;//返回本次增广的流量 } inline double Dinic() { double ans = 0; while(Bfs()) { for(int i = 0; i <= node_num; ++i) cur[i] = head[i]; ans += dfs(S, inf); } return ans; } void build(double mid) { cnt = 0; memset(head, -1, sizeof(head)); for (int i = 1; i <= m; ++ i) Add_Edge(n+i, tx[i], inf), Add_Edge(n+i, ty[i], inf); for (int i = 1; i <= m; ++ i) Add_Edge(S, n+i, 1.0); for (int i = 1; i <= n; ++ i) Add_Edge(i, T, mid); } void mark(int u) { vis[u] = 1; for (int i = head[u]; ~i; i = edge[i].next) { int v = edge[i].to; if (!edge[i].w || vis[v]) continue; mark(v); } } int main() { //freopen("out.txt","w",stdout); while (~scanf("%d%d", &n, &m)) { if (m == 0) { printf("1\n1\n"); continue;} for (int i = 1; i <= m; ++ i) scanf("%d%d", &tx[i], &ty[i]); S = 0, T = n+m+1, node_num=T; double L=0,R=m,M, eps=1.0/n/n; while (R - L > eps) { build(M = (L+R)/2); if ((double)m-Dinic() > 1e-8) L = M; else R = M; } //cout << L << endl; build(L); Dinic(); memset(vis, 0, sizeof(vis)); mark(S); int ans=0; for(int i=1;i<=n;i++) if(vis[i]) ans++; printf("%d\n",ans); for(int i=1;i<=n;i++) if(vis[i]) printf("%d\n",i); } return 0; } /* 5 6 1 5 5 4 4 2 2 5 1 2 3 1 */
附第二种方法的代码。

餐巾计划问题 #


#include <bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<ll, int> pii; struct Edge { int u, v; ll flow, cap, cost; int next; }; const int MAXN = 5000, MAXM = 50000; const ll INF = 0x3f3f3f3f3f3f3f3fLL; int e_ptr = 1, S, T, head[MAXN+10]; Edge E[(MAXM+10)<<1]; ll dist[MAXN+10], MaxFlow, MinCost, delta; int inq[MAXN+10], done[MAXN+10], vis[MAXN+10]; void AddEdge(int u, int v, ll cap, ll cost) { E[++e_ptr] = (Edge) { u, v, 0, cap, cost, head[u] }; head[u] = e_ptr; E[++e_ptr] = (Edge) { v, u, 0, 0, -cost, head[v] }; head[v] = e_ptr; } void Reduce() { for(int i = 2; i <= e_ptr; i++) E[i].cost += dist[E[i].v] - dist[E[i].u]; delta += dist[S]; } bool BellmanFord() { queue<int> Q; memset(dist, 0x3f, sizeof(dist)); dist[T] = 0; Q.push(T); inq[T] = true; while(!Q.empty()) { int u = Q.front(); Q.pop(); inq[u] = false; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost; if(f < c && dist[v] > dist[u] + len) { dist[v] = dist[u] + len; if(!inq[v]) { inq[v] = true; Q.push(v); } } } } return dist[S] != INF; } bool Dijkstra() { memset(dist, 0x3f, sizeof(dist)); memset(vis, 0, sizeof(vis)); priority_queue<pii,vector<pii>,greater<pii> > pq; dist[T] = 0; pq.push({dist[T], T}); while(!pq.empty()) { int u = pq.top().second; pq.pop(); if(vis[u]) continue; vis[u] = 1; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost; if(f < c && dist[v] > dist[u] + len) { dist[v] = dist[u] + len; pq.push({dist[v],v}); } } } return dist[S] != INF; } ll DFS(int u, ll flow) { if(u == T || flow == 0) return flow; vis[u] = true; // differ from dinic ll res = flow; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j].flow, c = E[j].cap, len = E[j].cost; if(!vis[v] && f < c && len == 0) { // not `dist[v] == dist[u]` ! they do not equal ! ll tmp = DFS(v, min(res, c-f)); // len = 0 <=> on the shortest path E[j].flow += tmp; E[j^1].flow -= tmp; res -= tmp; } } return flow - res; } void Augment() { ll CurFlow = 0; while(memset(vis, 0, sizeof(vis)), (CurFlow = DFS(S, INF))) { MaxFlow += CurFlow; MinCost += CurFlow * delta; } } void PrimalDual() { if(!BellmanFord()) return; Reduce(); Augment(); while(Dijkstra()) { Reduce(); Augment(); } } int main() { int N; scanf("%d", &N); S = 0, T = 2*N+1; for(int i = 1, x; i <= N; i++) { scanf("%d", &x); AddEdge(S, i, x, 0); //每天晚上收到x条脏毛巾 AddEdge(i+N, T, x, 0); //每天早上需要送出x条干净的毛巾 } int p, m, f, s, n; scanf("%d%d%d%d%d", &p, &m, &f, &n, &s); for (int i = 1; i <= N; ++ i) { if (i + 1 <= N) AddEdge(i, i+1, INF, 0); //每天晚上可以把剩下的毛巾留到明天晚上 if (i + m <= N) AddEdge(i, i+m+N, INF, f); //把晚上的毛巾送去洗到m天后的早上 if (i + n <= N) AddEdge(i, i+n+N, INF, s);//把晚上的毛巾送去洗到n天后的早上 AddEdge(S, i+n, INF, p); //每天早上可以买毛巾 } PrimalDual(); printf("%lld", MinCost); return 0; }
方格取数加强版 #
这道题太裸了。唯一需要注意的点就是需要连一条用作经过的边。

#include <bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<ll, int> pii; struct Edge { int u, v; ll flow, cap, cost; int next; }; const int MAXN = 5000, MAXM = 50000; const ll INF = 0x3f3f3f3f3f3f3f3fLL; int e_ptr = 1, S, T, head[MAXN+10]; Edge E[(MAXM+10)<<1]; ll dist[MAXN+10], MaxFlow, MinCost, delta; int inq[MAXN+10], done[MAXN+10], vis[MAXN+10]; void AddEdge(int u, int v, ll cap, ll cost) { E[++e_ptr] = (Edge) { u, v, 0, cap, cost, head[u] }; head[u] = e_ptr; E[++e_ptr] = (Edge) { v, u, 0, 0, -cost, head[v] }; head[v] = e_ptr; } void Reduce() { for(int i = 2; i <= e_ptr; i++) E[i].cost += dist[E[i].v] - dist[E[i].u]; delta += dist[S]; } bool BellmanFord() { queue<int> Q; memset(dist, 0x3f, sizeof(dist)); dist[T] = 0; Q.push(T); inq[T] = true; while(!Q.empty()) { int u = Q.front(); Q.pop(); inq[u] = false; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost; if(f < c && dist[v] > dist[u] + len) { dist[v] = dist[u] + len; if(!inq[v]) { inq[v] = true; Q.push(v); } } } } return dist[S] != INF; } bool Dijkstra() { memset(dist, 0x3f, sizeof(dist)); memset(vis, 0, sizeof(vis)); priority_queue<pii,vector<pii>,greater<pii> > pq; dist[T] = 0; pq.push({dist[T], T}); while(!pq.empty()) { int u = pq.top().second; pq.pop(); if(vis[u]) continue; vis[u] = 1; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost; if(f < c && dist[v] > dist[u] + len) { dist[v] = dist[u] + len; pq.push({dist[v],v}); } } } return dist[S] != INF; } ll DFS(int u, ll flow) { if(u == T || flow == 0) return flow; vis[u] = true; // differ from dinic ll res = flow; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j].flow, c = E[j].cap, len = E[j].cost; if(!vis[v] && f < c && len == 0) { // not `dist[v] == dist[u]` ! they do not equal ! ll tmp = DFS(v, min(res, c-f)); // len = 0 <=> on the shortest path E[j].flow += tmp; E[j^1].flow -= tmp; res -= tmp; } } return flow - res; } void Augment() { ll CurFlow = 0; while(memset(vis, 0, sizeof(vis)), (CurFlow = DFS(S, INF))) { MaxFlow += CurFlow; MinCost += CurFlow * delta; } } void PrimalDual() { if(!BellmanFord()) return; Reduce(); Augment(); while(Dijkstra()) { Reduce(); Augment(); } } int g[55][55]; int dic[2][2] = {1,0,0,1}; int main() { int n, 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", &g[i][j]); AddEdge(S, 1, k, 0), AddEdge(n*n*2, T, k, 0); for (int i = 1; i <= n; ++i) { for (int j = 1; j <= n; ++j) { int id = (i-1)*n+j; AddEdge(id, id+n*n, 1, -g[i][j]), AddEdge(id, id+n*n, INF, 0); for (int k = 0; k < 2; ++ k) { int tx = i+dic[k][0], ty = j+dic[k][1]; if (tx > n || ty > n) continue; int _id = (tx-1)*n+ty; AddEdge(id+n*n, _id, INF, 0); } } } PrimalDual(); printf("%lld", -MinCost); return 0; }
[SCOI2007]修车 #

#include <bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<ll, int> pii; struct Edge { int u, v; ll flow, cap, cost; int next; }; const int MAXN = 5000, MAXM = 50000; const ll INF = 0x3f3f3f3f3f3f3f3fLL; int e_ptr = 1, S, T, head[MAXN+10]; Edge E[(MAXM+10)<<1]; ll dist[MAXN+10], MaxFlow, MinCost, delta; int inq[MAXN+10], done[MAXN+10], vis[MAXN+10]; void AddEdge(int u, int v, ll cap, ll cost) { E[++e_ptr] = (Edge) { u, v, 0, cap, cost, head[u] }; head[u] = e_ptr; E[++e_ptr] = (Edge) { v, u, 0, 0, -cost, head[v] }; head[v] = e_ptr; } void Reduce() { for(int i = 2; i <= e_ptr; i++) E[i].cost += dist[E[i].v] - dist[E[i].u]; delta += dist[S]; } bool BellmanFord() { queue<int> Q; memset(dist, 0x3f, sizeof(dist)); dist[T] = 0; Q.push(T); inq[T] = true; while(!Q.empty()) { int u = Q.front(); Q.pop(); inq[u] = false; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost; if(f < c && dist[v] > dist[u] + len) { dist[v] = dist[u] + len; if(!inq[v]) { inq[v] = true; Q.push(v); } } } } return dist[S] != INF; } bool Dijkstra() { memset(dist, 0x3f, sizeof(dist)); memset(vis, 0, sizeof(vis)); priority_queue<pii,vector<pii>,greater<pii> > pq; dist[T] = 0; pq.push({dist[T], T}); while(!pq.empty()) { int u = pq.top().second; pq.pop(); if(vis[u]) continue; vis[u] = 1; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost; if(f < c && dist[v] > dist[u] + len) { dist[v] = dist[u] + len; pq.push({dist[v],v}); } } } return dist[S] != INF; } ll DFS(int u, ll flow) { if(u == T || flow == 0) return flow; vis[u] = true; // differ from dinic ll res = flow; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j].flow, c = E[j].cap, len = E[j].cost; if(!vis[v] && f < c && len == 0) { // not `dist[v] == dist[u]` ! they do not equal ! ll tmp = DFS(v, min(res, c-f)); // len = 0 <=> on the shortest path E[j].flow += tmp; E[j^1].flow -= tmp; res -= tmp; } } return flow - res; } void Augment() { ll CurFlow = 0; while(memset(vis, 0, sizeof(vis)), (CurFlow = DFS(S, INF))) { MaxFlow += CurFlow; MinCost += CurFlow * delta; } } void PrimalDual() { if(!BellmanFord()) return; Reduce(); Augment(); while(Dijkstra()) { Reduce(); Augment(); } } int cost[65][10]; int main() { int n, m; scanf("%d%d", &m, &n); //n车,m人 S = 0, T = m*n+n+1; for (int i = 1; i <= m; ++ i) for (int j = 1; j <= n; ++ j) AddEdge((i-1)*n+j+n, T, 1, 0); for (int i = 1; i <= n; ++ i) { //最外层是车 AddEdge(S, i, 1, 0); for (int j = 1; j <= m; ++ j) { //这一层是人 int temp; scanf("%d", &temp); for (int k = 1; k <= n; ++ k) //这一层是枚举时间段 AddEdge(i, n+(j-1)*n+k, 1, k*temp); } } PrimalDual(); printf("%.2f\n", 1.0*MinCost/n); return 0; }
[NOI2012]美食节 #
修车的加强版。区别就是每款车都有个数了,所以时间段建点会很多,会导致爆时间。
具体的优化就是需要根据每次哪个人倒数第几次修车被跑满了,就动态加入新的时间段。
附上嫖自洛谷的代码。

#include <iostream> #include <iomanip> #include <cstring> #include <map> #include <queue> #define inf 2147483646 #define N 10000 using namespace std; struct ed{ int u,w,next,f; }e[1000000]; int g[1000][2000],a[2000]; int n,m,st=1,ans,cost,sm,fir[30000],c[30000],d[30000]; int vis[30000],sp[30000]; queue<int> q; bool v[30000]; map<int,int> ha; void add(int x,int y,int w,int f) { e[++st].u=y; e[st].next=fir[x]; e[fir[x]=st].w=w; e[st].f=f; e[++st].u=x; e[st].next=fir[y]; e[fir[y]=st].w=0; e[st].f=-f; } bool spfa() { for (int i=0;i<=N;i++) d[i]=inf/2,c[i]=fir[i],v[i]=0; q.push(0); v[0]=1; d[0]=0; while (!q.empty()) { int k=q.front(); q.pop(); v[k]=0; for (int i=fir[k];i;i=e[i].next){ int u=e[i].u,w=e[i].f; if (d[u]>d[k]+w&&e[i].w){ d[u]=d[k]+w; if (!v[u]) v[u]=1,q.push(u); } } } return (d[N]<inf/2); } int dfs(int p,int now) { if (p==N){v[N]=1; return now;} int mw=0; v[p]=1; for (int i=fir[p];i;i=e[i].next) { int u=e[i].u,w=e[i].f; if (d[u]==d[p]+w&&e[i].w&&(!v[u]||u==N)) if (mw=dfs(u,min(e[i].w,now))) { e[i].w-=mw; e[i^1].w+=mw; cost+=mw*w; return mw; } } } void dinic() { while (spfa()) { ans+=dfs(0,inf); for (int i=fir[N];i;i=e[i].next){ int u=e[i].u,w=e[i].w; if (w&&!vis[u]) { vis[u]=1; int co=ha[u]; sp[co]++; add(++sm,N,1,0); ha[sm]=co; for (int i=1;i<=n;i++) add(i,sm,1,sp[co]*g[i][co]); } } } } int main() { cin>>n>>m; int sum=0; for (int i=1;i<=n;i++) cin>>a[i],sum+=a[i]; for (int i=1;i<=n;i++) for (int j=1;j<=m;j++) cin>>g[i][j]; for (int i=1;i<=n;i++) add(0,i,a[i],0); sm=n; //for (int k=1;k<=n;k++) 时间K(总数不为n了) for (int j=1;j<=m;j++) {//厨师j add(++sm,N,1,0); ha[sm]=j; sp[j]=1; for (int i=1;i<=n;i++) add(i,sm,1,g[i][j]); //菜i } dinic(); cout<<cost<<endl; }
[SDOI2009]晨跑 #
路线不会相交,所以每个点只能走一遍。直接拆点,出点和入点连费用为0容量为1的边。
对于每条有向边,起点出点连终点入点,容量为1,费用为输入费用。
关于代码中S = 1+n(1号点的出点),T = n(n号点的入点),是因为S和T是可以重复经过的。

#include <bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<ll, int> pii; struct Edge { int u, v; ll flow, cap, cost; int next; }; const int MAXN = 5000, MAXM = 50000; const ll LLINF = 0x3f3f3f3f3f3f3f3fLL; int n, m; int e_ptr = 1, S, T, head[MAXN+10]; Edge E[(MAXM+10)<<1]; ll dist[MAXN+10], MaxFlow, MinCost, delta; int inq[MAXN+10], done[MAXN+10], vis[MAXN+10]; void AddEdge(int u, int v, ll cap, ll cost) { E[++e_ptr] = (Edge) { u, v, 0, cap, cost, head[u] }; head[u] = e_ptr; E[++e_ptr] = (Edge) { v, u, 0, 0, -cost, head[v] }; head[v] = e_ptr; } void Reduce() { for(int i = 2; i <= e_ptr; i++) E[i].cost += dist[E[i].v] - dist[E[i].u]; delta += dist[S]; } bool BellmanFord() { queue<int> Q; memset(dist, 0x3f, sizeof(dist)); dist[T] = 0; Q.push(T); inq[T] = true; while(!Q.empty()) { int u = Q.front(); Q.pop(); inq[u] = false; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost; if(f < c && dist[v] > dist[u] + len) { dist[v] = dist[u] + len; if(!inq[v]) { inq[v] = true; Q.push(v); } } } } return dist[S] != LLINF; } bool Dijkstra() { memset(dist, 0x3f, sizeof(dist)); memset(vis, 0, sizeof(vis)); priority_queue<pii,vector<pii>,greater<pii> > pq; dist[T] = 0; pq.push({dist[T], T}); while(!pq.empty()) { int u = pq.top().second; pq.pop(); if(vis[u]) continue; vis[u] = 1; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost; if(f < c && dist[v] > dist[u] + len) { dist[v] = dist[u] + len; pq.push({dist[v],v}); } } } return dist[S] != LLINF; } ll DFS(int u, ll flow) { if(u == T || flow == 0) return flow; vis[u] = true; // differ from dinic ll res = flow; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j].flow, c = E[j].cap, len = E[j].cost; if(!vis[v] && f < c && len == 0) { // not `dist[v] == dist[u]` ! they do not equal ! ll tmp = DFS(v, min(res, c-f)); // len = 0 <=> on the shortest path E[j].flow += tmp; E[j^1].flow -= tmp; res -= tmp; } } return flow - res; } void Augment() { ll CurFlow = 0; while(memset(vis, 0, sizeof(vis)), (CurFlow = DFS(S, LLINF))) { MaxFlow += CurFlow; MinCost += CurFlow * delta; } } void PrimalDual() { if(!BellmanFord()) return; Reduce(); Augment(); while(Dijkstra()) { Reduce(); Augment(); } } int main() { scanf("%d%d", &n, &m); S = n+1, T = n; for (int i = 1; i <= n; ++ i) AddEdge(i, i+n, 1, 0); for(int i=1; i<=m; i++) { int u, v, cost; scanf("%d%d%d", &u, &v, &cost); AddEdge(u+n, v, 1, cost); } PrimalDual(); printf("%lld %lld", MaxFlow, MinCost); return 0; }
第一道最小路径覆盖的费用流的题目。
再费用流中,最小路径覆盖就不能像在最大流中一样了。
但是背后的思想都是一样的。
在最大流中的最小路径覆盖的方法是:先将n个点拆点为i和i′。然后由源点向所有的i点连一条容量为1的边,再由所有的i'点向汇点连一条容量为1的边,对于每条边u−>v,由u向v'连一条容量为1的边,跑一遍最大流后,n减去最大流就是最小路径覆盖。
为什么可以这样做呢?可以发现,上面其实是一个二分图,最大流实际上就是最大匹配数。在这里可以发现,匹配中每连一条边,路径的数量就减1
所以路径数 = n - 最大流
算是给自己长个见识把。

#include <bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<ll, int> pii; struct Edge { int u, v; ll flow, cap, cost; int next; }; const int MAXN = 5000, MAXM = 50000; const ll LLINF = 0x3f3f3f3f3f3f3f3fLL; int e_ptr = 1, S, T, n, m, head[MAXN+10]; Edge E[(MAXM+10)<<1]; ll dist[MAXN+10], MaxFlow, MinCost, delta; int inq[MAXN+10], done[MAXN+10], vis[MAXN+10]; void AddEdge(int u, int v, ll cap, ll cost) { E[++e_ptr] = (Edge) { u, v, 0, cap, cost, head[u] }; head[u] = e_ptr; E[++e_ptr] = (Edge) { v, u, 0, 0, -cost, head[v] }; head[v] = e_ptr; } void Reduce() { for(int i = 2; i <= e_ptr; i++) E[i].cost += dist[E[i].v] - dist[E[i].u]; delta += dist[S]; } bool BellmanFord() { queue<int> Q; memset(dist, 0x3f, sizeof(dist)); dist[T] = 0; Q.push(T); inq[T] = true; while(!Q.empty()) { int u = Q.front(); Q.pop(); inq[u] = false; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost; if(f < c && dist[v] > dist[u] + len) { dist[v] = dist[u] + len; if(!inq[v]) { inq[v] = true; Q.push(v); } } } } return dist[S] != LLINF; } bool Dijkstra() { memset(dist, 0x3f, sizeof(dist)); memset(vis, 0, sizeof(vis)); priority_queue<pii,vector<pii>,greater<pii> > pq; dist[T] = 0; pq.push({dist[T], T}); while(!pq.empty()) { int u = pq.top().second; pq.pop(); if(vis[u]) continue; vis[u] = 1; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost; if(f < c && dist[v] > dist[u] + len) { dist[v] = dist[u] + len; pq.push({dist[v],v}); } } } return dist[S] != LLINF; } ll DFS(int u, ll flow) { if(u == T || flow == 0) return flow; vis[u] = true; // differ from dinic ll res = flow; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j].flow, c = E[j].cap, len = E[j].cost; if(!vis[v] && f < c && len == 0) { // not `dist[v] == dist[u]` ! they do not equal ! ll tmp = DFS(v, min(res, c-f)); // len = 0 <=> on the shortest path E[j].flow += tmp; E[j^1].flow -= tmp; res -= tmp; } } return flow - res; } void Augment() { ll CurFlow = 0; while(memset(vis, 0, sizeof(vis)), (CurFlow = DFS(S, LLINF))) { MaxFlow += CurFlow; MinCost += CurFlow * delta; } } void PrimalDual() { if(!BellmanFord()) return; Reduce(); Augment(); while(Dijkstra()) { Reduce(); Augment(); } } int main() { int u, v, cap, cost; scanf("%d%d", &n, &m); S = 0, T = n+n+1; for (int i = 1; i <= n; i ++) { int temp; scanf("%d", &temp); AddEdge(S, i+n, 1, temp); AddEdge(S, i, 1, 0); AddEdge(i+n, T, 1, 0); } for (int i = 1, u, v, c; i <= m; i ++) { scanf("%d%d%d", &u, &v, &c); if (u > v) swap(u, v); AddEdge(u, v+n, 1, c); } PrimalDual(); printf("%lld\n", MinCost); return 0; }
[HAOI2010]订货 #

#include <bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<ll, int> pii; struct Edge { int u, v; ll flow, cap, cost; int next; }; const int MAXN = 5000, MAXM = 50000; const ll LLINF = 0x3f3f3f3f3f3f3f3fLL; int n, m; int e_ptr = 1, S, T, head[MAXN+10]; Edge E[(MAXM+10)<<1]; ll dist[MAXN+10], MaxFlow, MinCost, delta; int inq[MAXN+10], done[MAXN+10], vis[MAXN+10]; void AddEdge(int u, int v, ll cap, ll cost) { E[++e_ptr] = (Edge) { u, v, 0, cap, cost, head[u] }; head[u] = e_ptr; E[++e_ptr] = (Edge) { v, u, 0, 0, -cost, head[v] }; head[v] = e_ptr; } void Reduce() { for(int i = 2; i <= e_ptr; i++) E[i].cost += dist[E[i].v] - dist[E[i].u]; delta += dist[S]; } bool BellmanFord() { queue<int> Q; memset(dist, 0x3f, sizeof(dist)); dist[T] = 0; Q.push(T); inq[T] = true; while(!Q.empty()) { int u = Q.front(); Q.pop(); inq[u] = false; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost; if(f < c && dist[v] > dist[u] + len) { dist[v] = dist[u] + len; if(!inq[v]) { inq[v] = true; Q.push(v); } } } } return dist[S] != LLINF; } bool Dijkstra() { memset(dist, 0x3f, sizeof(dist)); memset(vis, 0, sizeof(vis)); priority_queue<pii,vector<pii>,greater<pii> > pq; dist[T] = 0; pq.push({dist[T], T}); while(!pq.empty()) { int u = pq.top().second; pq.pop(); if(vis[u]) continue; vis[u] = 1; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost; if(f < c && dist[v] > dist[u] + len) { dist[v] = dist[u] + len; pq.push({dist[v],v}); } } } return dist[S] != LLINF; } ll DFS(int u, ll flow) { if(u == T || flow == 0) return flow; vis[u] = true; // differ from dinic ll res = flow; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j].flow, c = E[j].cap, len = E[j].cost; if(!vis[v] && f < c && len == 0) { // not `dist[v] == dist[u]` ! they do not equal ! ll tmp = DFS(v, min(res, c-f)); // len = 0 <=> on the shortest path E[j].flow += tmp; E[j^1].flow -= tmp; res -= tmp; } } return flow - res; } void Augment() { ll CurFlow = 0; while(memset(vis, 0, sizeof(vis)), (CurFlow = DFS(S, LLINF))) { MaxFlow += CurFlow; MinCost += CurFlow * delta; } } void PrimalDual() { if(!BellmanFord()) return; Reduce(); Augment(); while(Dijkstra()) { Reduce(); Augment(); } } int main() { int store; scanf("%d%d%d", &n, &m, &store); S = 0, T = 2*n+1; for (int i = 1; i <= n; ++ i) { int temp; scanf("%d", &temp); AddEdge(i, T, temp, 0); // AddEdge(i, i+n, store, 0); if (i < n) AddEdge(i, i+1, store, m); } for (int i = 1; i <= n; ++ i) { int temp; scanf("%d", &temp); AddEdge(S, i, LLINF, temp); } PrimalDual(); printf("%lld\n", MinCost); return 0; }
[ZJOI2010]网络扩容 #

#include <bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<ll, int> pii; struct Edge { int u, v; ll flow, cap, cost; int next; }; const int MAXN = 5000+10, MAXM = 50000; const ll LLINF = 0x3f3f3f3f3f3f3f3fLL; int n, m, k; int e_ptr = 1, S, T, head[MAXN]; Edge E[(MAXM)<<1]; ll dist[MAXN], MaxFlow, MinCost, delta; int inq[MAXN], done[MAXN], vis[MAXN]; int color[MAXN]; void AddEdge(int u, int v, ll cap, ll cost) { E[++e_ptr] = (Edge) { u, v, 0, cap, cost, head[u] }; head[u] = e_ptr; E[++e_ptr] = (Edge) { v, u, 0, 0, -cost, head[v] }; head[v] = e_ptr; } void Reduce() { for(int i = 2; i <= e_ptr; i++) E[i].cost += dist[E[i].v] - dist[E[i].u]; delta += dist[S]; } bool BellmanFord() { queue<int> Q; memset(dist, 0x3f, sizeof(dist)); dist[T] = 0; Q.push(T); inq[T] = true; while(!Q.empty()) { int u = Q.front(); Q.pop(); inq[u] = false; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost; if(f < c && dist[v] > dist[u] + len) { dist[v] = dist[u] + len; if(!inq[v]) { inq[v] = true; Q.push(v); } } } } return dist[S] != LLINF; } bool Dijkstra() { memset(dist, 0x3f, sizeof(dist)); memset(vis, 0, sizeof(vis)); priority_queue<pii,vector<pii>,greater<pii> > pq; dist[T] = 0; pq.push({dist[T], T}); while(!pq.empty()) { int u = pq.top().second; pq.pop(); if(vis[u]) continue; vis[u] = 1; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost; if(f < c && dist[v] > dist[u] + len) { dist[v] = dist[u] + len; pq.push({dist[v],v}); } } } return dist[S] != LLINF; } ll DFS(int u, ll flow) { if(u == T || flow == 0) return flow; vis[u] = true; // differ from dinic ll res = flow; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j].flow, c = E[j].cap, len = E[j].cost; if(!vis[v] && f < c && len == 0) { // not `dist[v] == dist[u]` ! they do not equal ! ll tmp = DFS(v, min(res, c-f)); // len = 0 <=> on the shortest path E[j].flow += tmp; E[j^1].flow -= tmp; res -= tmp; } } return flow - res; } void Augment() { ll CurFlow = 0; while(memset(vis, 0, sizeof(vis)), (CurFlow = DFS(S, LLINF))) { MaxFlow += CurFlow; MinCost += CurFlow * delta; } } void PrimalDual() { if(!BellmanFord()) return; Reduce(); Augment(); while(Dijkstra()) { Reduce(); Augment(); } } struct record { int u, v; ll w, c; }rec[MAXN]; int main() { scanf("%d%d%d", &n, &m, &k); S = 1, T = n; for (int i = 1,u,v,c; i <= m; ++ i) { scanf("%d%d%d%d", &rec[i].u, &rec[i].v, &rec[i].c, &rec[i].w); AddEdge(rec[i].u, rec[i].v, rec[i].c, 0); } PrimalDual(); printf("%lld ", MaxFlow); T = n+1; memset(head, 0, sizeof(head)), e_ptr = 1; AddEdge(n, T, MaxFlow+k, 0); for (int i = 1,u,v,c; i <= m; ++ i) { AddEdge(rec[i].u, rec[i].v, rec[i].c, 0); AddEdge(rec[i].u, rec[i].v, LLINF, rec[i].w); } ll rec_MinCost = MinCost; PrimalDual(); printf("%lld\n", MinCost); return 0; }
P2770 航空路线问题 #
这道题的DFS遍历非常值得学习,因为必然是从 i + n 到 j 。
所以我们进入的点必然是 i + n 点, 出去的点必然是 j ,所以我们就直接跳到 j +n 就代表经过 j 点。

#include <bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<ll, int> pii; struct Edge { int u, v; ll flow, cap, cost; int next; }; const int MAXN = 5000, MAXM = 50000; const ll LLINF = 0x3f3f3f3f3f3f3f3fLL; int e_ptr = 1, S, T, n, m, head[MAXN+10]; Edge E[(MAXM+10)<<1]; ll dist[MAXN+10], MaxFlow, MinCost, delta; int inq[MAXN+10], done[MAXN+10], vis[MAXN+10]; void AddEdge(int u, int v, ll cap, ll cost) { E[++e_ptr] = (Edge) { u, v, 0, cap, cost, head[u] }; head[u] = e_ptr; E[++e_ptr] = (Edge) { v, u, 0, 0, -cost, head[v] }; head[v] = e_ptr; } void Reduce() { for(int i = 2; i <= e_ptr; i++) E[i].cost += dist[E[i].v] - dist[E[i].u]; delta += dist[S]; } bool BellmanFord() { queue<int> Q; memset(dist, 0x3f, sizeof(dist)); dist[T] = 0; Q.push(T); inq[T] = true; while(!Q.empty()) { int u = Q.front(); Q.pop(); inq[u] = false; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost; if(f < c && dist[v] > dist[u] + len) { dist[v] = dist[u] + len; if(!inq[v]) { inq[v] = true; Q.push(v); } } } } return dist[S] != LLINF; } bool Dijkstra() { memset(dist, 0x3f, sizeof(dist)); memset(vis, 0, sizeof(vis)); priority_queue<pii,vector<pii>,greater<pii> > pq; dist[T] = 0; pq.push({dist[T], T}); while(!pq.empty()) { int u = pq.top().second; pq.pop(); if(vis[u]) continue; vis[u] = 1; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost; if(f < c && dist[v] > dist[u] + len) { dist[v] = dist[u] + len; pq.push({dist[v],v}); } } } return dist[S] != LLINF; } ll DFS(int u, ll flow) { if(u == T || flow == 0) return flow; vis[u] = true; // differ from dinic ll res = flow; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j].flow, c = E[j].cap, len = E[j].cost; if(!vis[v] && f < c && len == 0) { // not `dist[v] == dist[u]` ! they do not equal ! ll tmp = DFS(v, min(res, c-f)); // len = 0 <=> on the shortest path E[j].flow += tmp; E[j^1].flow -= tmp; res -= tmp; } } return flow - res; } void Augment() { ll CurFlow = 0; while(memset(vis, 0, sizeof(vis)), (CurFlow = DFS(S, LLINF))) { MaxFlow += CurFlow; MinCost += CurFlow * delta; } } void PrimalDual() { if(!BellmanFord()) return; Reduce(); Augment(); while(Dijkstra()) { Reduce(); Augment(); } } map<string, int> name; map<int, string> num; int color[MAXN]; void DFS1(int u) { color[u]=1; cout << num[u-n] << endl; for(int i=head[u];i;i=E[i].next) if(E[i].v<=n&&E[i].cap > 0 && E[i].cap == E[i].flow) { DFS1(E[i].v+n); break; } } void DFS2(int u) { for(int i=head[u];i;i=E[i].next) if(E[i].v<=n&&!color[E[i].v+n]&& E[i].cap == E[i].flow) DFS2(E[i].v+n); cout<<num[u-n]<<endl; } int main() { cin >> n >> m; S = 1, T = n+n; int flag = 0; AddEdge(S, 1+n, 2, -1), AddEdge(n, T, 2, -1); for (int i = 1; i <= n; ++ i) { string city; cin >> city; name[city] = i, num[i] = city; if (i == 1 || i == n) continue; AddEdge(name[city], name[city]+n, 1, -1); } for (int i = 1; i <= m; ++ i) { string u, v; cin >> u >> v; if (name[u] > name[v]) swap(u, v); AddEdge(name[u]+n, name[v], 1, 0); if (name[u] == 1 && name[v] == n) flag = 1; } PrimalDual(); if (MaxFlow != 2 && !flag) printf("No Solution!\n"); else if (MaxFlow == 1 && flag) { cout << 2 << endl; cout << num[1] << endl << num[n] << endl << num[1] << endl; } else { cout << -MinCost-2 << endl; DFS1(n+1), DFS2(1+n); } return 0; }

#include <bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<ll, int> pii; struct Edge { int u, v; ll flow, cap, cost; int next; }; const int MAXN = 5000+10, MAXM = 50000; const ll LLINF = 0x3f3f3f3f3f3f3f3fLL; int n, m, k; int e_ptr = 1, S, T, head[MAXN]; Edge E[(MAXM)<<1]; ll dist[MAXN], MaxFlow, MinCost, delta; int inq[MAXN], done[MAXN], vis[MAXN]; void AddEdge(int u, int v, ll cap, ll cost) { E[++e_ptr] = (Edge) { u, v, 0, cap, cost, head[u] }; head[u] = e_ptr; E[++e_ptr] = (Edge) { v, u, 0, 0, -cost, head[v] }; head[v] = e_ptr; } void Reduce() { for(int i = 2; i <= e_ptr; i++) E[i].cost += dist[E[i].v] - dist[E[i].u]; delta += dist[S]; } bool BellmanFord() { queue<int> Q; memset(dist, 0x3f, sizeof(dist)); dist[T] = 0; Q.push(T); inq[T] = true; while(!Q.empty()) { int u = Q.front(); Q.pop(); inq[u] = false; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost; if(f < c && dist[v] > dist[u] + len) { dist[v] = dist[u] + len; if(!inq[v]) { inq[v] = true; Q.push(v); } } } } return dist[S] != LLINF; } bool Dijkstra() { memset(dist, 0x3f, sizeof(dist)); memset(vis, 0, sizeof(vis)); priority_queue<pii,vector<pii>,greater<pii> > pq; dist[T] = 0; pq.push({dist[T], T}); while(!pq.empty()) { int u = pq.top().second; pq.pop(); if(vis[u]) continue; vis[u] = 1; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost; if(f < c && dist[v] > dist[u] + len) { dist[v] = dist[u] + len; pq.push({dist[v],v}); } } } return dist[S] != LLINF; } ll DFS(int u, ll flow) { if(u == T || flow == 0) return flow; vis[u] = true; // differ from dinic ll res = flow; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j].flow, c = E[j].cap, len = E[j].cost; if(!vis[v] && f < c && len == 0) { // not `dist[v] == dist[u]` ! they do not equal ! ll tmp = DFS(v, min(res, c-f)); // len = 0 <=> on the shortest path E[j].flow += tmp; E[j^1].flow -= tmp; res -= tmp; } } return flow - res; } void Augment() { ll CurFlow = 0; while(memset(vis, 0, sizeof(vis)), (CurFlow = DFS(S, LLINF))) { MaxFlow += CurFlow; MinCost += CurFlow * delta; } } void PrimalDual() { if(!BellmanFord()) return; Reduce(); Augment(); while(Dijkstra()) { Reduce(); Augment(); } } char st[25][25]; char ed[25][25]; char times[25][25]; int dic[8][2] = {1,0, 0,1, -1,0, 0,-1, 1,1, 1,-1, -1,1, -1,-1}; int main() { cin >> n >> m; S = 0, T = n*m*3+1; int cnt1 = 0, cnt2 = 0; for (int i = 1; i <= n; ++ i) for (int j = 1; j <= m; ++ j) cin >> st[i][j], cnt1 += st[i][j]-'0'; for (int i = 1; i <= n; ++ i) for (int j = 1; j <= m; ++ j) cin >> ed[i][j], cnt2 += ed[i][j]-'0'; for (int i = 1; i <= n; ++ i) for (int j = 1; j <= m; ++ j) cin >> times[i][j]; // 不加就是pre , + n*m就是mid, + 2*n*m就是aft for (int i = 1; i <= n; ++ i) { for (int j = 1; j <= m; ++ j) { int id = (i-1)*m+j; int limit = times[i][j]-'0'; if (st[i][j] == '1') AddEdge(S, id+n*m, 1, 0); if (ed[i][j] == '1') AddEdge(id+n*m, T, 1, 0); if (st[i][j] == '0' && ed[i][j] == '1') AddEdge(id, id+n*m, (limit+1)/2, 0), AddEdge(id+n*m, id+2*n*m, limit/2, 0); else if (st[i][j] == '1' && ed[i][j] == '0') AddEdge(id, id+n*m, limit/2, 0), AddEdge(id+n*m, id+2*n*m, (limit+1)/2, 0); else AddEdge(id, id+n*m, limit/2, 0), AddEdge(id+n*m, id+2*n*m, limit/2, 0); for (int k = 0; k < 8; ++ k) { int tx = i+dic[k][0], ty = j+dic[k][1]; if (tx < 1 || ty < 1 || tx > n || ty > m) continue; int id1 = (tx-1)*m + ty; AddEdge(id+2*n*m, id1, LLINF, 1); } } } PrimalDual(); if (cnt1 != cnt2 || MaxFlow != cnt1) cout << -1 << endl; else cout << MinCost << endl; return 0; }
P3356 火星探险问题 #
主要就是路径输出,看来我已经掌握了路径输出的要点了。
这道题因为没有用最大费用导致debug了好久。好累。
今天把讨厌的人删了,还是挺舒服的。
建图不是很难,我都想到了

#include <bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<ll, int> pii; struct Edge { int u, v; ll flow, cap, cost; int next; }; const int MAXN = 5000+10, MAXM = 50000; const ll LLINF = 0x3f3f3f3f3f3f3f3fLL; int n, m, k; int e_ptr = 1, S, T, head[MAXN]; Edge E[(MAXM)<<1]; ll dist[MAXN], MaxFlow, MinCost, delta; int inq[MAXN], done[MAXN], vis[MAXN]; void AddEdge(int u, int v, ll cap, ll cost) { E[++e_ptr] = (Edge) { u, v, 0, cap, cost, head[u] }; head[u] = e_ptr; E[++e_ptr] = (Edge) { v, u, 0, 0, -cost, head[v] }; head[v] = e_ptr; } void Reduce() { for(int i = 2; i <= e_ptr; i++) E[i].cost += dist[E[i].v] - dist[E[i].u]; delta += dist[S]; } bool BellmanFord() { queue<int> Q; memset(dist, 0x3f, sizeof(dist)); dist[T] = 0; Q.push(T); inq[T] = true; while(!Q.empty()) { int u = Q.front(); Q.pop(); inq[u] = false; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost; if(f < c && dist[v] > dist[u] + len) { dist[v] = dist[u] + len; if(!inq[v]) { inq[v] = true; Q.push(v); } } } } return dist[S] != LLINF; } bool Dijkstra() { memset(dist, 0x3f, sizeof(dist)); memset(vis, 0, sizeof(vis)); priority_queue<pii,vector<pii>,greater<pii> > pq; dist[T] = 0; pq.push({dist[T], T}); while(!pq.empty()) { int u = pq.top().second; pq.pop(); if(vis[u]) continue; vis[u] = 1; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost; if(f < c && dist[v] > dist[u] + len) { dist[v] = dist[u] + len; pq.push({dist[v],v}); } } } return dist[S] != LLINF; } ll DFS(int u, ll flow) { if(u == T || flow == 0) return flow; vis[u] = true; // differ from dinic ll res = flow; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j].flow, c = E[j].cap, len = E[j].cost; if(!vis[v] && f < c && len == 0) { // not `dist[v] == dist[u]` ! they do not equal ! ll tmp = DFS(v, min(res, c-f)); // len = 0 <=> on the shortest path E[j].flow += tmp; E[j^1].flow -= tmp; res -= tmp; } } return flow - res; } void Augment() { ll CurFlow = 0; while(memset(vis, 0, sizeof(vis)), (CurFlow = DFS(S, LLINF))) { MaxFlow += CurFlow; MinCost += CurFlow * delta; } } void PrimalDual() { if(!BellmanFord()) return; Reduce(); Augment(); while(Dijkstra()) { Reduce(); Augment(); } } int g[36][36]; int dic[2][2] = {1,0, 0,1}; void FindPath(int tms, int u) { for (int i = head[u]; i; i = E[i].next) { int v = E[i].v; if (v == u-n*m) continue; if (v == T || v == S) continue; if (!E[i].flow) continue; E[i].flow--; int pos = (v == u-n*m+1); cout << tms << " " << pos << endl; FindPath(tms, v+n*m); break; } } int main() { int num; scanf("%d", &num); scanf("%d%d", &m, &n); S = 0, T = n*m*2+1; for (int i = 1; i <= n; ++ i) for (int j = 1; j <= m; ++ j) scanf("%d", &g[i][j]); AddEdge(S, 1, num, 0), AddEdge(n*m*2, T, LLINF, 0); for (int i = 1; i <= n; ++ i) { for (int j = 1; j <= m; ++ j) { int id = (i-1)*m+j; AddEdge(id, id+n*m, LLINF, 0); if (g[i][j] == 1) continue ; if (g[i][j] == 2) AddEdge(id, id+n*m, 1, -1); for (int k = 0; k < 2; ++ k) { int tx = i+dic[k][0], ty = j+dic[k][1]; int _id = (tx-1)*m+ty; if (ty > m || tx > n || g[tx][ty] == 1) continue; AddEdge(id+n*m, _id, LLINF, 0); } } } PrimalDual(); for (int i = 1; i <= MaxFlow; ++ i) FindPath(i, 1+n*m); return 0; }
P3440 [POI2006]SZK-Schools #
看数据范围就知道很暴力。直接将200范围全部拆点,暴力连就好了。跑出来最大流不为n就输出NIE。

#include <bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<ll, int> pii; struct Edge { int u, v; ll flow, cap, cost; int next; }; const int MAXN = 5000+10, MAXM = 50000; const ll LLINF = 0x3f3f3f3f3f3f3f3fLL; int n, m, k; int e_ptr = 1, S, T, head[MAXN]; Edge E[(MAXM)<<1]; ll dist[MAXN], MaxFlow, MinCost, delta; int inq[MAXN], done[MAXN], vis[MAXN]; void AddEdge(int u, int v, ll cap, ll cost) { E[++e_ptr] = (Edge) { u, v, 0, cap, cost, head[u] }; head[u] = e_ptr; E[++e_ptr] = (Edge) { v, u, 0, 0, -cost, head[v] }; head[v] = e_ptr; } void Reduce() { for(int i = 2; i <= e_ptr; i++) E[i].cost += dist[E[i].v] - dist[E[i].u]; delta += dist[S]; } bool BellmanFord() { queue<int> Q; memset(dist, 0x3f, sizeof(dist)); dist[T] = 0; Q.push(T); inq[T] = true; while(!Q.empty()) { int u = Q.front(); Q.pop(); inq[u] = false; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost; if(f < c && dist[v] > dist[u] + len) { dist[v] = dist[u] + len; if(!inq[v]) { inq[v] = true; Q.push(v); } } } } return dist[S] != LLINF; } bool Dijkstra() { memset(dist, 0x3f, sizeof(dist)); memset(vis, 0, sizeof(vis)); priority_queue<pii,vector<pii>,greater<pii> > pq; dist[T] = 0; pq.push({dist[T], T}); while(!pq.empty()) { int u = pq.top().second; pq.pop(); if(vis[u]) continue; vis[u] = 1; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost; if(f < c && dist[v] > dist[u] + len) { dist[v] = dist[u] + len; pq.push({dist[v],v}); } } } return dist[S] != LLINF; } ll DFS(int u, ll flow) { if(u == T || flow == 0) return flow; vis[u] = true; // differ from dinic ll res = flow; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j].flow, c = E[j].cap, len = E[j].cost; if(!vis[v] && f < c && len == 0) { // not `dist[v] == dist[u]` ! they do not equal ! ll tmp = DFS(v, min(res, c-f)); // len = 0 <=> on the shortest path E[j].flow += tmp; E[j^1].flow -= tmp; res -= tmp; } } return flow - res; } void Augment() { ll CurFlow = 0; while(memset(vis, 0, sizeof(vis)), (CurFlow = DFS(S, LLINF))) { MaxFlow += CurFlow; MinCost += CurFlow * delta; } } void PrimalDual() { if(!BellmanFord()) return; Reduce(); Augment(); while(Dijkstra()) { Reduce(); Augment(); } } struct Node { } sch[205]; int main() { int n; scanf("%d", &n); S = 0, T = 200+n+1; for (int i = 1; i <= n; ++ i) { int mi, ai, bi, ki; scanf("%d %d %d %d", &mi, &ai, &bi, &ki); for (int j = ai; j <= bi; ++ j) AddEdge(i, n+j, 1, ki*abs(j-mi)); AddEdge(S, i, 1, 0); } for (int i = 1; i <= 200; ++ i) AddEdge(n+i, T, 1, 0); PrimalDual(); if (MaxFlow != n) cout << "NIE" << endl; else cout << MinCost << endl; return 0; }
P3705 [SDOI2017]新生舞会 #
我的原始对偶板子好像不是很适合求这种分数规划费用流的题目,这算是给个板子吧。思想是不难的。

#include<bits/stdc++.h> using namespace std; int read() { char cc = getchar(); int cn = 0, flus = 1; while(cc < '0' || cc > '9') { if( cc == '-' ) flus = -flus; cc = getchar(); } while(cc >= '0' && cc <= '9') cn = cn * 10 + cc - '0', cc = getchar(); return cn * flus; } const int M = 2e5 + 5 ; const int N = 200 + 5 ; #define inf 123456789 #define eps 1e-8 #define rep( i, s, t ) for( register int i = s; i <= t; ++ i ) #define Next( i, x ) for( register int i = head[x]; i; i = e[i].next ) struct E { int to, next, w; double f ; } e[M * 2]; int head[N * 2], cur[N * 2], cnt, n, mark[N * 2], vis[N * 2], s, t ; double dis[N * 2], Ans, a[N][N], b[N][N] ; void add( int x, int y, int z, double f ) { e[++ cnt] = (E){ y, head[x], z, f }, head[x] = cnt ; e[++ cnt] = (E){ x, head[y], 0, -f }, head[y] = cnt ; } queue< int > q; bool spfa() { rep( i, s, t ) dis[i] = - inf ; memset( vis, 0, sizeof(vis) ) ; q.push(s) ; dis[s] = 0 ; while( !q.empty() ) { int u = q.front() ; q.pop() ; vis[u] = 0; Next( i, u ) { int v = e[i].to ; if( dis[v] < dis[u] + e[i].f && e[i].w ) { dis[v] = dis[u] + e[i].f; if( !vis[v] ) q.push(v), vis[v] = 1; } } } return dis[t] != -inf ; } int dfs( int x, int dist ) { mark[x] = 1 ; if( x == t ) return dist ; int flow = 0 ; for( register int &i = cur[x]; i; i = e[i].next ) { int v = e[i].to ; if( !mark[v] && dis[v] == dis[x] + e[i].f && e[i].w ) { int di = dfs( v, min( dist, e[i].w ) ) ; if( di > 0 ) { e[i].w -= di, e[i ^ 1].w += di; flow += di, dist -= di ; Ans += di * e[i].f ; if( dist == 0 ) return flow ; } } } return flow ; } void zkwflow() { Ans = 0; while(spfa()) { memcpy( cur, head, sizeof(head) ) ; mark[t] = 1 ; while( mark[t] ) { memset( mark, 0, sizeof(mark) ) ; dfs( s, inf ) ; } } } bool check( double x ) { s = 0, t = 2 * n + 1 ; cnt = 1; memset( head, 0, sizeof(head) ); rep( i, 1, n ) add( s, i, 1, 0 ), add( i + n, t, 1, 0 ) ; rep( i, 1, n ) rep( j, 1, n ) add( i, j + n, 1, a[i][j] - x * b[i][j] ); zkwflow() ; return Ans >= 0 ; } void solve() { double l = 0, r = 10000 + 5, mid, ans = 0 ; while( r - l > eps ) { mid = ( l + r ) / 2.0 ; if( check(mid) ) ans = mid, l = mid + eps ; else r = mid - eps ; } printf("%.6lf\n", ans ) ; } signed main() { n = read() ; rep( i, 1, n ) rep( j, 1, n ) a[i][j] = read() ; rep( i, 1, n ) rep( j, 1, n ) b[i][j] = read() ; solve() ; return 0; }

#include <bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<ll, int> pii; struct Edge { int u, v; ll flow, cap, cost; int next; }; const int MAXN = 5000+10, MAXM = 50000; const ll LLINF = 0x3f3f3f3f3f3f3f3fLL; int n, m, k; int e_ptr = 1, S, T, head[MAXN]; Edge E[(MAXM)<<1]; ll dist[MAXN], MaxFlow, MinCost, delta; int inq[MAXN], done[MAXN], vis[MAXN]; void AddEdge(int u, int v, ll cap, ll cost) { E[++e_ptr] = (Edge) { u, v, 0, cap, cost, head[u] }; head[u] = e_ptr; E[++e_ptr] = (Edge) { v, u, 0, 0, -cost, head[v] }; head[v] = e_ptr; } void Reduce() { for(int i = 2; i <= e_ptr; i++) E[i].cost += dist[E[i].v] - dist[E[i].u]; delta += dist[S]; } bool BellmanFord() { queue<int> Q; memset(dist, 0x3f, sizeof(dist)); dist[T] = 0; Q.push(T); inq[T] = true; while(!Q.empty()) { int u = Q.front(); Q.pop(); inq[u] = false; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost; if(f < c && dist[v] > dist[u] + len) { dist[v] = dist[u] + len; if(!inq[v]) { inq[v] = true; Q.push(v); } } } } return dist[S] != LLINF; } bool Dijkstra() { memset(dist, 0x3f, sizeof(dist)); memset(vis, 0, sizeof(vis)); priority_queue<pii,vector<pii>,greater<pii> > pq; dist[T] = 0; pq.push({dist[T], T}); while(!pq.empty()) { int u = pq.top().second; pq.pop(); if(vis[u]) continue; vis[u] = 1; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j^1].flow, c = E[j^1].cap, len = E[j^1].cost; if(f < c && dist[v] > dist[u] + len) { dist[v] = dist[u] + len; pq.push({dist[v],v}); } } } return dist[S] != LLINF; } ll DFS(int u, ll flow) { if(u == T || flow == 0) return flow; vis[u] = true; // differ from dinic ll res = flow; for(int j=head[u]; j; j=E[j].next) { int v = E[j].v; ll f = E[j].flow, c = E[j].cap, len = E[j].cost; if(!vis[v] && f < c && len == 0) { // not `dist[v] == dist[u]` ! they do not equal ! ll tmp = DFS(v, min(res, c-f)); // len = 0 <=> on the shortest path E[j].flow += tmp; E[j^1].flow -= tmp; res -= tmp; } } return flow - res; } void Augment() { ll CurFlow = 0; while(memset(vis, 0, sizeof(vis)), (CurFlow = DFS(S, LLINF))) { MaxFlow += CurFlow; MinCost += CurFlow * delta; } } void PrimalDual() { if(!BellmanFord()) return; Reduce(); Augment(); while(Dijkstra()) { Reduce(); Augment(); } } map<char, int> DicHash; int g[20][20]; int dic[4][2] = {-1,0, 1,0, 0,-1, 0,1}; int main() { DicHash['U'] = 0, DicHash['D'] = 1; DicHash['L'] = 2, DicHash['R'] = 3; cin >> n >> m; S = 0, T = n*m*2+1; for (int i = 1; i <= n; ++ i) { for (int j = 1; j <= m; ++ j) { char ch; cin >> ch, g[i][j] = DicHash[ch]; int id = (i-1)*m+j; AddEdge(S, id, 1, 0), AddEdge(id+n*m, T, 1, 0); for (int k = 0; k < 4; ++ k) { int tx = (i+dic[k][0]+n-1)%n+1, ty = (dic[k][1]+j+m-1)%m+1; int _id = (tx-1)*m+ty; AddEdge(id, _id+n*m, 1, g[i][j] != k); } } } PrimalDual(); cout << MinCost << endl; return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人