网络流24题
飞行员配对
\(dinic\) 跑二分图最大匹配,然后输出方案
const int N = 300 + 5;
const int M = 100000;
int head[N], ver[M], nxt[M], edge[M], d[N];
int n, m, s, t, tot, maxflow;
queue<int> q;
void add(int x, int y, int z){
ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x],head[x] = tot;
ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot;
}
bool bfs(){
memset(d, 0, sizeof d);
while(q.size())q.pop();
q.push(s);d[s] = 1;
while(q.size()){
int x = q.front();q.pop();
for(int i=head[x];i;i=nxt[i]){
if(edge[i] && !d[ver[i]]){
d[ver[i]] = d[x] + 1;
q.push(ver[i]);
if(ver[i] == t)return true;
}
}
}
return false;
}
int dinic(int x, int flow){
if(x == t)return flow;
int rest = flow, k;
for(int i=head[x];i&&rest;i=nxt[i]){
if(edge[i] && d[ver[i]] == d[x] + 1){
k = dinic(ver[i], min(rest, edge[i]));
if(!k) d[ver[i]] = 0;
edge[i] -= k;
edge[i^1] += k;
rest -= k;
}
}
return flow - rest;
}
int main() {
scanf("%d%d",&m,&n);
tot = 1;
int x, y, z;
while(scanf("%d%d",&x,&y) == 2 && x != -1 && y != -1){
add(x, y, 1);
}
s = n + m + 1, t = n + m + 2;
for(int i=1;i<=m;i++)add(s, i, 1);
for(int i=m+1;i<=m+n;i++) add(i, t, 1);
int flow = 0;
while(bfs()) while(flow = dinic(s, inf)) maxflow += flow;
printf("%d\n",maxflow);
for(int i=1;i<=m;i++){
for(int j=head[i];j;j=nxt[j]){
if(ver[j] == s || edge[j])continue;
printf("%d %d\n",i, ver[j]);
break;
}
}
return 0;
}
圆桌问题
s 向每个桌子连流量为\(c[i]\) 的边,每个桌子向每个团队连流量为 \(1\) 的边,每个团队向 \(t\) 连流量为 \(r[i]\) 的边。跑最大流
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 500 + 5;
const int M = 200000;
int head[N], ver[M], edge[M], nxt[M], d[N];
int r[N], c[N];
int n, m, s, t, tot, maxflow;
queue<int> q;
void add(int x, int y, int z){
ver[++tot]=y,edge[tot]=z,nxt[tot]=head[x],head[x]=tot;
ver[++tot]=x,edge[tot]=0,nxt[tot]=head[y],head[y]=tot;
}
bool bfs(){
while(q.size())q.pop();
memset(d, 0, sizeof d);
d[s] = 1;q.push(s);
while(q.size()){
int x = q.front();q.pop();
for(int i=head[x];i;i=nxt[i]){
if(edge[i] && !d[ver[i]]){
d[ver[i]] = d[x] + 1;
q.push(ver[i]);
if(ver[i] == t)return true;
}
}
}
return false;
}
int dinic(int x, int flow){
if(x == t)return flow;
int rest = flow, k;
for(int i=head[x];i && rest;i=nxt[i]){
if(edge[i] && d[ver[i]] == d[x] + 1){
k = dinic(ver[i], min(edge[i], flow));
if(!k) d[ver[i]] = 0;
edge[i] -= k;
edge[i^1] += k;
rest -= k;
}
}
return flow - rest;
}
int main() {
scanf("%d%d",&m,&n);
int sum = 0;
tot = 1;
for(int i=1;i<=m;i++)scanf("%d",&r[i]),sum+=r[i];
for(int i=1;i<=n;i++)scanf("%d",&c[i]);
s = n + m + 1, t = n + m + 2;
for(int i=1;i<=n;i++) add(s, i, c[i]);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) add(i,j+n,1);
for(int i=1;i<=m;i++) add(i+n, t, r[i]);
int flow = 0;
while(bfs())while(flow = dinic(s, inf)) maxflow += flow;
if(maxflow != sum){
puts("0");return 0;
}
puts("1");
for(int x=n+1;x<=n+m;x++){
for(int i=head[x];i;i=nxt[i]){
if(edge[i] == 0)continue;
printf("%d ", ver[i]);
}
puts("");
}
return 0;
}
最小路径覆盖问题
拆点,建图,跑最大流
const int inf = 0x3f3f3f3f;
const int N = 400 + 5;
const int M = 20010;
int head[N], ver[M], edge[M], nxt[M], d[N];
int vis[N];
int n, m, s, t, tot, maxflow;
queue<int> q;
void add(int x, int y, int z){
ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot;
ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot;
}
bool bfs(){
memset(d, 0, sizeof d);
while(q.size())q.pop();
q.push(s);d[s] = 1;
while(q.size()){
int x = q.front();q.pop();
for(int i=head[x];i;i=nxt[i]){
if(edge[i] && !d[ver[i]]){
q.push(ver[i]);
d[ver[i]] = d[x] + 1;
if(ver[i] == t) return 1;
}
}
}
return 0;
}
int dinic(int x, int flow){
if(x == t) return flow;
int rest = flow, k;
for(int i=head[x];i && rest; i=nxt[i]){
if(edge[i] && d[ver[i]] == d[x] + 1){
k = dinic(ver[i], min(rest, edge[i]));
if(!k) d[ver[i]] = 0;
edge[i] -= k;
edge[i ^ 1] += k;
rest -= k;
}
}
return flow - rest;
}
void get(int x){
int t = x;
bool flag;
do{
printf("%d ", t);
vis[t] = 1;
flag = false;
for(int i=head[t];i;i=nxt[i]){
if(ver[i] == s || edge[i])continue;
t = ver[i] - n;
flag = true;
break;
}
}while(flag);
puts("");
}
int main() {
scanf("%d%d",&n,&m);
tot = 1;
for(int i=1;i<=m;i++){
int x,y;scanf("%d%d",&x,&y);
add(x, y + n, 1);
}
s = 2 * n + 1, t = s + 1;
for(int i=1;i<=n;i++){
add(s, i, 1);
add(i+n, t, 1);
}
int flow = 0;
while(bfs())
while(flow = dinic(s, inf)) maxflow += flow;
for(int i=1;i<=n;i++){
if(!vis[i]){
get(i);
}
}
printf("%d\n", n - maxflow);
return 0;
}
最长不下降子序列
特判n=1的情况
设 \(d[i]\) 为以 i 结尾的最长不下降子序列长度
若 \(d[i] == 1\), 则 \(s -> i\) 连一条边
若 \(d[j] + 1 == d[i] ~\& ~ a[j] <= a[i]\),则 $j' -> i $连一条边
若 \(d[i] == maxlen\), 则$ i‘ -> t$ 连一条边
$i -> i' $连一条边
边权都为1,跑最大流
第三问要修改 \((s, 1), (1,1'), (n, n'), (n', t)\) 边权为\(\infin\)
#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 1000 + 5;
const int M = 600000;
int head[N], ver[M], edge[M], nxt[M], tot;
int d[N],a[N],v[N];
int n, s, t, maxflow;
void add(int x, int y, int z){
ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot;
ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot;
}
queue<int> q;
bool bfs(){
memset(d, 0, sizeof d);
memset(v, 0, sizeof v);
while(q.size())q.pop();
q.push(s), d[s] = 1;
while(q.size()){
int x = q.front();q.pop();
for(int i=head[x];i;i=nxt[i]){
if(edge[i] && !d[ver[i]]){
d[ver[i]] = d[x] + 1;
q.push(ver[i]);
if(ver[i] == t) return true;
}
}
}
return false;
}
int dinic(int x, int flow){
if(x == t)return flow;
int rest = flow, k;
for(int i=head[x];i && rest;i=nxt[i]){
if(edge[i] && d[ver[i]] == d[x] + 1){
k = dinic(ver[i], min(rest, edge[i]));
if(!k) d[ver[i]] = 0;
edge[i] -= k;
edge[i^1] += k;
rest -= k;
}
}
return flow - rest;
}
int main() {
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
int mx = 0;
tot = 1;
for(int i=1;i<=n;i++){
d[i] = 1;
for(int j=1;j<i;j++){
if(a[i] >= a[j]) {
d[i] = max(d[i], d[j]+1);
}
}
mx = max(mx, d[i]);
}
s = 2 * n + 1, t = s + 1;
for(int i=1;i<=n;i++){
if(d[i] == 1) add(s, i, 1);
if(d[i] == mx) add(i+n, t, 1);
add(i, i+n, 1);
for(int j=1;j<i;j++){
if(a[i] >= a[j] && d[i] == d[j] + 1){
add(j+n, i, 1);
}
}
}
int flow = 0;
while(bfs()) while(flow = dinic(s, inf)) maxflow += flow;
printf("%d\n", mx);
printf("%d\n", maxflow);
for(int i=head[s];i;i=nxt[i])if(ver[i] == 1) edge[i] = inf;
for(int i=head[1];i;i=nxt[i])if(ver[i] == 1+n) edge[i] = inf;
for(int i=head[n];i;i=nxt[i]) if(ver[i] == 2*n) edge[i] = inf;
for(int i=head[2*n];i;i=nxt[i]) if(ver[i] == t) edge[i] = inf;
while(bfs()) while(flow = dinic(s, inf)) maxflow += flow;
if(n == 1) maxflow = 1;
printf("%d\n", maxflow);
return 0;
}
方格取数(最大点权独立集)
- 满足每一条边的两个端点至少选一个的最小权点集
- 满足每一条边的两个端点至多选一个是最大权点集
这两个集合互为补集,二分图左部点与 s 连边权为该点点权的边,右部与 t 连边权为该点点权的点,原二分图的边边权设置为\(\infin\) ,跑最小割(最大流),被割掉的那些边,就是不能选的边(即一一对应原来二分图中的点)。
#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 10000 + 5;
const int M = 100000;
int head[N], ver[M], edge[M], nxt[M], tot;
int d[N], a[101][101],v[N];
int n, m, s, t, maxflow;
void add(int x, int y, int z){
ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot;
ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot;
}
queue<int> q;
bool bfs(){
memset(d, 0, sizeof d);
memset(v, 0, sizeof v);
while(q.size())q.pop();
q.push(s), d[s] = 1;
while(q.size()){
int x = q.front();q.pop();
for(int i=head[x];i;i=nxt[i]){
if(edge[i] && !d[ver[i]]){
d[ver[i]] = d[x] + 1;
q.push(ver[i]);
if(ver[i] == t) return true;
}
}
}
return false;
}
int dinic(int x, int flow){
if(x == t)return flow;
int rest = flow, k;
for(int i=head[x];i && rest;i=nxt[i]){
if(edge[i] && d[ver[i]] == d[x] + 1){
k = dinic(ver[i], min(rest, edge[i]));
if(!k) d[ver[i]] = 0;
edge[i] -= k;
edge[i^1] += k;
rest -= k;
}
}
return flow - rest;
}
inline int id(int x, int y){return (x-1)*m+y;}
int main() {
scanf("%d%d",&n,&m);
s = n * m + 1, t = s + 1;
tot = 1;
int sum = 0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%d",&a[i][j]);
if((i+j)%2 == 0){
add(s, id(i, j), a[i][j]);
if(i > 1) add(id(i, j), id(i-1, j), inf);
if(j > 1) add(id(i, j), id(i, j-1), inf);
if(i < n) add(id(i, j), id(i+1, j), inf);
if(j < m) add(id(i, j), id(i, j+1), inf);
}
else add(id(i, j), t, a[i][j]);
sum += a[i][j];
}
}
int flow = 0;
while(bfs()) while(flow = dinic(s, inf)) maxflow += flow;
sum -= maxflow;
printf("%d\n",sum);
return 0;
}
/*
3 3
1 2 3
3 2 3
2 3 1
*/
太空飞行计划问题
最大权闭合图,选谁必选谁
S 向正权点连容量为该点权的边, 负权点向T连容量为该点权绝对值的边,原来的边容量全部为\(\infin\)
最小割之后,与 S 联通点都要选,与T联通的点都不选
#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
#include <sstream>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 100 + 5;
const int M = 100010;
int head[N], ver[M], edge[M], nxt[M], tot;
int d[N], v[N];
int n, m, s, t, maxflow;
void add(int x, int y, int z){
ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot;
ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot;
}
queue<int> q;
bool bfs(){
memset(d, 0, sizeof d);
memset(v, 0, sizeof v);
while(q.size())q.pop();
q.push(s), d[s] = 1;
while(q.size()){
int x = q.front();q.pop();
for(int i=head[x];i;i=nxt[i]){
if(edge[i] && !d[ver[i]]){
cout << x << ' ' << ver[i] << endl;
d[ver[i]] = d[x] + 1;
q.push(ver[i]);
if(ver[i] == t) return true;
}
}
}
return false;
}
int dinic(int x, int flow){
if(x == t)return flow;
int rest = flow, k;
for(int i=head[x];i && rest;i=nxt[i]){
if(edge[i] && d[ver[i]] == d[x] + 1){
k = dinic(ver[i], min(rest, edge[i]));
if(!k) d[ver[i]] = 0;
edge[i] -= k;
edge[i^1] += k;
rest -= k;
}
}
return flow - rest;
}
int main() {
scanf("%d%d",&n,&m);
getchar();
tot = 1;s = n + m + 1, t = s + 1;
int sum = 0;
for(int i=1;i<=n;i++){
string t;
getline(cin, t);
stringstream ss(t);
bool flag = true;
int x = 0;
while(ss >> x){
if(flag) {
sum += x;
add(s, i, x);
}
else{
add(i, x+n, inf);
}
flag = false;
}
}
for(int i=1;i<=m;i++){
int x;scanf("%d",&x);
add(i+n, t, x);
}
int flow = 0;
while(bfs())while(flow = dinic(s, inf)) maxflow += flow;
sum -= maxflow;
//可以利用最后一次bfs的信息判断是否联通
bool flag = true;
for(int i=1;i<=n;i++) if(d[i] && flag) printf("%d",i),flag = false;else if(d[i]) printf(" %d",i);
puts("");
flag = true;
for(int i=1;i<=m;i++) if(d[i+n] && flag) printf("%d",i),flag = false;else if(d[i+n]) printf(" %d",i);
puts("");
printf("%d\n",sum);
return 0;
}
负载均衡问题
裸的均分纸牌问题,可以\(O(n\log n)\) 时间解决
网络流的可以这样考虑,设平均值为 \(ave\)
对于权值大于等于 \(ave\) 的点,连接 \(s\rightarrow i\) ,边权为 \(a[i] -ave\) ,费用\(0\)
对于权值小于等于\(ave\) 的点,连接\(i\rightarrow t\) , 边权为\(ave-a[i]\), 费用 \(0\) ,
对于相邻的点:连接\(i\rightarrow j\) ,边权\(inf\), 费用为 \(1\)
#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 100000 + 5;
int n, a[N], s[N];
int main() {
cin >> n;
int sum = 0;
for(int i=1;i<=n;i++) cin >> a[i], sum += a[i];
sum /= n;
for(int i=1;i<=n;i++) a[i] -= sum,s[i] = s[i-1] + a[i];
sort(s + 1, s + 1 + n);
int res = 0;
for(int i=1;i<=n;i++) res += abs(s[i] - s[(n+1)/2]);
cout << res << endl;
return 0;
}
软件补丁问题
\(spfa\)或者\(dijkstra\)
边数过多可以不加边,直接在转移时判断
#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 20;
const int M = 101;
int n, m, s, t;
int d[1<<20], v[1<<20];
int c[M], b1[M], b2[M], f1[M], f2[M];
char str[101];
void dijkstra(){
memset(d, 0x3f,sizeof d);
priority_queue<pair<int,int> > q;
q.push({0, s});d[s] = 0;
while(q.size()){
int x = q.top().second;q.pop();
if(v[x])continue;
v[x] = 1;
for(int i=1;i<=m;i++){
if((x&b1[i]) == b1[i] && (x&b2[i]) == 0){
int y = ((x|f1[i])^f1[i]) | f2[i];
if(d[y] > d[x] + c[i]){
d[y] = d[x] + c[i];
q.push({-d[y], y});
}
}
}
}
}
int main() {
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d",&c[i]);
scanf("%s",str);
for(int j=0;j<n;j++)if(str[j] == '+')b1[i] |= 1 << j;else if(str[j] == '-') b2[i] |= 1 << j;
scanf("%s",str);
for(int j=0;j<n;j++)if(str[j] == '-')f1[i] |= 1 << j;else if(str[j] == '+') f2[i] |= 1 << j;
}
s = (1 << n) - 1;
dijkstra();
if(d[t] == inf)puts("0");
else printf("%d\n",d[t]);
return 0;
}
孤岛营救问题
\(e[x1][y1][x2][y2]\) 表示\((x1,y1) \rightarrow (x2,y2)\) 门的情况,-1表示畅通,0表示不可逾越,否则状压表示所需的钥匙
\(d[x][y][i]\) 表示到达\((x,y)\) 位置,拥有钥匙状态为 i 时的最短时间
\(key[x][y]\) 表示 \((x,y)\) 位置的钥匙情况
由于转移条件每次只会增加1的时间,所以可以直接\(bfs\)
#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 11;
int e[N][N][N][N], key[N][N];
int d[N][N][1<<N], v[N][N][1<<N];
int n, m, p, k, s;
queue<pair<pair<int,int>,int>>q;
int dx[4] = {0,0,-1,1}, dy[4] = {1,-1,0,0};
#define mk make_pair
int dijkstra(){
memset(d, 0x3f, sizeof d);
q.push(mk(mk(1,1),key[1][1]));
d[1][1][key[1][1]] = 0;
while(q.size()){
auto t = q.front();q.pop();
int x = t.first.first, y = t.first.second, keys = t.second;
for(int i=0;i<4;i++){
int nx = x + dx[i], ny = y + dy[i];
if(nx < 1 || nx > n || ny < 1 || ny > m) continue;
if(e[x][y][nx][ny] == 0)continue;
if(e[x][y][nx][ny] > 0 && (keys & e[x][y][nx][ny]) == 0)continue;
int nkeys = keys | key[nx][ny];
//printf("(%d,%d,%d)->(%d,%d,%d)\n",x,y,keys,nx,ny,nkeys);
if(v[nx][ny][nkeys])continue;
d[nx][ny][nkeys] = d[x][y][keys] + 1;
v[nx][ny][nkeys] = 1;
q.push(mk(mk(nx,ny),nkeys));
if(nx == n && ny == m) return d[nx][ny][nkeys];
}
}
return -1;
}
int main() {
scanf("%d%d%d%d",&n,&m,&p,&k);
memset(e, -1, sizeof e);
for(int i=1;i<=k;i++){
int x1,y1,x2,y2,t;
scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&t);
if(t == 0)
e[x1][y1][x2][y2] = e[x2][y2][x1][y1] = t;
else e[x1][y1][x2][y2] = e[x2][y2][x1][y1] = 1 << (t-1);
}
scanf("%d",&s);
for(int i=1;i<=s;i++){
int x, y, t;
scanf("%d%d%d",&x,&y,&t);
key[x][y] |= 1 << (t-1);
}
printf("%d\n",dijkstra());
return 0;
}
星际转移
首先用并查集判断是否联通,然后确定有没有解。
猜想答案不会很大,所以每次增加一次时间,就多建一层图,从源点向所有的地球连inf的边,由于人类可以停留不动,所以上一层到这一层的对应节点之间连inf的边,然后上一层与这一层可以发生太空船转移的,加h[i]
容量的边
#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 1000000 + 5;
const int M = 1000010;
int head[N], ver[M], nxt[M], edge[M], d[N], tot;
int f[50], h[50], num[50], g[50][50];
int n, m, k, s, t;
int maxflow, flow, res;
void add(int x, int y, int z){
ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot;
ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot;
}
int find(int x){return x == f[x] ? x : f[x] = find(f[x]);}
void merge(int x, int y){
x = find(x), y = find(y);
if(x != y) f[x] = y;
}
bool bfs(){
for(int i=0;i<=res*(n+1);i++) d[i] = 0;
d[t] = 0;
queue<int> q;
q.push(s);d[s] = 1;
while(q.size()){
int x = q.front();q.pop();
for(int i=head[x];i;i=nxt[i]){
if(edge[i] && !d[ver[i]]){
d[ver[i]] = d[x] + 1;
q.push(ver[i]);
if(ver[i] == t) return true;
}
}
}
return false;
}
int dinic(int x, int flow){
if(x == t) return flow;
int rest = flow, k;
for(int i=head[x];i&&rest;i=nxt[i]){
if(edge[i] && d[ver[i]] == d[x] + 1){
k = dinic(ver[i], min(edge[i], rest));
if(!k) d[ver[i]] = 0;
edge[i] -= k;
edge[i^1] += k;
rest -= k;
}
}
return flow - rest;
}
int main() {
scanf("%d%d%d",&n,&m,&k);
s = 0, t = 1000000;
for(int i=1;i<=n+2;i++)f[i] = i;
for(int i=1;i<=m;i++){
scanf("%d%d",&h[i], &num[i]);
for(int j=0;j<num[i];j++){
scanf("%d",&g[i][j]);
if(g[i][j] == -1) g[i][j] = n+2;
if(g[i][j] == 0) g[i][j] = n+1;
if(j) merge(g[i][j-1], g[i][j]);
}
}
if(find(n+1) != find(n+2)){
puts("0");
return 0;
}
tot = 1;
for(res=1;;res++){
add(s, res * (n+1), inf);
for(int i=1;i<=m;i++){
int x = (res-1)%num[i], y = res % num[i];
if(g[i][x] == n+2) x = t;
else x = (res-1)*(n+1) + g[i][x];
if(g[i][y] == n+2) y = t;
else y = res*(n+1) + g[i][y];
add(x, y, h[i]);
}
while(bfs()) while(flow = dinic(s, inf)) maxflow += flow;
if(maxflow >= k){
printf("%d\n", res);
return 0;
}
for(int i=1;i<=n+1;i++)add((res-1)*(n+1)+i, res*(n+1)+i, inf);
}
return 0;
}
餐巾计划
每一天要产生 \(r_i\) 个脏餐巾 -> s向i连流量为\(r_i\),费用为0的边
每一天要使用 \(r_i\) 个餐巾 -> i'向t连流量为\(r_i\),费用为0的边
每一天可以购买餐巾,s 向 i' 连流量为inf,费用为p的边
脏的餐巾可以送到快洗店 -> i 向 (i+m)' 连流量为inf,费用为f的边
脏的餐巾可以送到慢洗店 -> i 向 (i+n)' 连流量为inf,费用为s的边
#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 4000 + 5;
const int M = 1000010;
int ver[M], edge[M], cost[M], nxt[M], head[N];
int pre[N], d[N], incf[N], v[N];
int n, k, tot, s, t, p, kx, mx, fkx, fmx;
ll maxflow, ans;
void add(int x,int y,int z,int c){
ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot, cost[tot] = c;
ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot, cost[tot] = -c;
}
bool spfa(){
queue<int> q;
memset(d, 0x3f, sizeof d);
memset(v, 0, sizeof v);
q.push(s);
d[s] = 0, v[s] = 1;
incf[s] = inf;
while(q.size()){
int x = q.front();
q.pop();
v[x] = 0;
for (int i = head[x]; i;i=nxt[i]){
if(!edge[i])
continue;
int y = ver[i];
if(d[y] > d[x] + cost[i]){
d[y] = d[x] + cost[i];
incf[y] = min(incf[x], edge[i]);
pre[y] = i;
if(!v[y])
v[y] = 1, q.push(y);
}
}
}
if(d[t] == inf)
return false;
return true;
}
void update(){
int x = t;
while(x != s){
int i = pre[x];
edge[i] -= incf[t];
edge[i ^ 1] += incf[t];
x = ver[i ^ 1];
}
maxflow += incf[t];
ans += 1ll * d[t] * incf[t];
}
int main() {
scanf("%d",&n);
tot = 1, s = 2 * n + 1, t = s + 1;
for(int i=1,x;i<=n;i++){
scanf("%d",&x);
add(s, i, x, 0);
add(i+n, t, x, 0);
}
scanf("%d%d%d%d%d",&p,&kx,&fkx,&mx,&fmx);
for(int i=1;i<=n;i++){
add(s, i+n, inf, p);
if(i < n) add(i, i+1, inf, 0);
if(i + kx <= n) add(i, i+kx+n, inf, fkx);
if(i + mx <= n) add(i, i+mx+n, inf, fmx);
}
while(spfa())update();
printf("%lld\n",ans);
return 0;
}
试题库问题
s 向每种类别连边,流量为其所需的题目数量
每种类别向对应的题目连边,流量为 1
每个题目向 t 连边,流量为 1
跑最大流,如果流量等于所需题目总数则输出方案,否则输出No Solution!
#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 2000 + 5;
const int M = 100010;
int head[N], ver[M], edge[M], nxt[M], d[N];
int n, m, k, s, t, tot, maxflow;
queue<int> q;
void add(int x, int y, int z){
ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot;
ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot;
}
bool bfs(){
memset(d, 0, sizeof d);
while(q.size())q.pop();
q.push(s);d[s] = 1;
while(q.size()){
int x = q.front();q.pop();
for(int i=head[x];i;i=nxt[i]){
if(edge[i] && !d[ver[i]]){
q.push(ver[i]);
d[ver[i]] = d[x] + 1;
if(ver[i] == t) return 1;
}
}
}
return 0;
}
int dinic(int x, int flow){
if(x == t) return flow;
int rest = flow, k;
for(int i=head[x];i && rest; i=nxt[i]){
if(edge[i] && d[ver[i]] == d[x] + 1){
k = dinic(ver[i], min(rest, edge[i]));
if(!k) d[ver[i]] = 0;
edge[i] -= k;
edge[i ^ 1] += k;
rest -= k;
}
}
return flow - rest;
}
int main() {
scanf("%d%d",&k,&n);
s = k + n + 1, t = s + 1;
tot = 1;
for(int i=1,x;i<=k;i++){
scanf("%d",&x);
add(s, i, x);
m+=x;
}
for(int i=1,p,x;i<=n;i++){
scanf("%d",&p);
add(i+k, t, 1);
while(p--){
scanf("%d",&x);
add(x, i+k, 1);
}
}
int flow = 0;
while(bfs())while(flow = dinic(s,inf))maxflow += flow;
if(maxflow < m){
puts("No Solution!");
return 0;
}
for(int i=1;i<=k;i++){
printf("%d:", i);
for(int j = head[i];j;j=nxt[j]){
if(ver[j] != s && edge[j] == 0){
printf(" %d", ver[j] - k);
}
}
puts("");
}
return 0;
}
最长k可重线段集问题
此题与最长 k 可重区间集问题有很大的相似之处,唯有一点不同的是,线段可以垂直于 x 轴,所以我们不能让 x -> x连边产生自环,所以对点进行拆点,每个点分为入状态和出状态两个状态。
#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 2000 + 5;
const int M = 1000010;
int n, k, l[N], r[N], l1[N], r1[N];
int ver[M], edge[M], nxt[M], head[N];
int incf[N], pre[N], v[N];
int tot, s, t;
ll cost[M], d[N], maxflow, ans, len[N];
vector<int> all;
void add(int x,int y,int z,ll c){
ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot, cost[tot] = c;
ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot, cost[tot] = -c;
}
bool spfa(){
queue<int> q;
memset(d, 0x3f, sizeof d);
memset(v, 0, sizeof v);
q.push(s);
d[s] = 0, v[s] = 1;
incf[s] = inf;
while(q.size()){
int x = q.front();
q.pop();
v[x] = 0;
for (int i = head[x]; i;i=nxt[i]){
if(!edge[i])
continue;
int y = ver[i];
if(d[y] > d[x] + cost[i]){
d[y] = d[x] + cost[i];
incf[y] = min(incf[x], edge[i]);
pre[y] = i;
if(!v[y])
v[y] = 1, q.push(y);
}
}
}
if(d[t] == 0x3f3f3f3f3f3f3f3f)
return false;
return true;
}
void update(){
int x = t;
while(x != s){
int i = pre[x];
edge[i] -= incf[t];
edge[i ^ 1] += incf[t];
x = ver[i ^ 1];
}
maxflow += incf[t];
ans += d[t] * incf[t];
}
ll S(ll x){return x * x;}
ll calc(int id){
return sqrt(S(l[id]-l1[id]) + S(r[id]-r1[id]));
}
int main() {
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++){
scanf("%d%d%d%d",&l[i],&r[i],&l1[i], &r1[i]);
if(l[i] > l1[i]) swap(l[i], l1[i]);
len[i] = calc(i);
l[i] <<= 1;
l1[i] <<= 1;
if(l[i] == l1[i]) l1[i]++; else l[i] ++;//这里的操作就是拆点操作,奇数点为出点,偶数点为入点
all.push_back(l[i]);all.push_back(l1[i]);
}
sort(all.begin(),all.end());
tot = 1, s = all.size(), t = s + 1;
add(s, 0, k, 0);
add(s-1, t, k, 0);
for(int i=0;i<all.size() - 1;i++){
add(i, i + 1, inf, 0);
}
for(int i=1;i<=n;i++){
int lid = lower_bound(all.begin(),all.end(),l[i]) - all.begin();
int rid = lower_bound(all.begin(), all.end(), l1[i]) - all.begin();
add(lid, rid, 1, -len[i]);
}
while(spfa())update();
cout << -ans << endl;
return 0;
}
最长k可重区间集问题
设值域范围为 [a,b]
,则 s 向 a 连流量 k,费用0的边,b 向 t 连流量 k,费用 0 的边,从 s 走到 t,[a,b)
中的每个点 i ,都向 i+1 连流量为 inf,费用为 0 的边。对于每一个输入给定的区间 [l,r],l 向 r 连流量为 1,费用为 r-l 的边,然后跑最大费用最大流即可。
#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 2000 + 5;
const int M = 1000010;
int n, k, l[N], r[N], id[N];
int ver[M], edge[M], cost[M], nxt[M], head[N];
int d[N], incf[N], pre[N], v[N];
int tot, s, t, maxflow, ans;
vector<int> all;
void add(int x,int y,int z,int c){
ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot, cost[tot] = c;
ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot, cost[tot] = -c;
}
bool spfa(){
queue<int> q;
memset(d, 0x3f, sizeof d);
memset(v, 0, sizeof v);
q.push(s);
d[s] = 0, v[s] = 1;
incf[s] = inf;
while(q.size()){
int x = q.front();
q.pop();
v[x] = 0;
for (int i = head[x]; i;i=nxt[i]){
if(!edge[i])
continue;
int y = ver[i];
if(d[y] > d[x] + cost[i]){
d[y] = d[x] + cost[i];
incf[y] = min(incf[x], edge[i]);
pre[y] = i;
if(!v[y])
v[y] = 1, q.push(y);
}
}
}
if(d[t] == inf)
return false;
return true;
}
void update(){
int x = t;
while(x != s){
int i = pre[x];
edge[i] -= incf[t];
edge[i ^ 1] += incf[t];
x = ver[i ^ 1];
}
maxflow += incf[t];
ans += d[t] * incf[t];
}
int main() {
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++){
scanf("%d%d",&l[i],&r[i]);
if(l[i] > r[i]) swap(l[i], r[i]);
all.push_back(l[i]);all.push_back(r[i]);
}
sort(all.begin(),all.end());
tot = 1, s = all.size(), t = s + 1;
add(s, 0, k, 0);
add(s-1, t, k, 0);
for(int i=0;i<all.size() - 1;i++){
add(i, i + 1, inf, 0);
}
for(int i=1;i<=n;i++){
int lid = lower_bound(all.begin(),all.end(),l[i]) - all.begin();
int rid = lower_bound(all.begin(), all.end(), r[i]) - all.begin();
add(lid, rid, 1, l[i]-r[i]);
}
while(spfa())update();
cout << -ans << endl;
return 0;
}
火星探险问题
将每个点拆为入点和出点,对于不是障碍的每个入点向出点连流量为 inf,费用为 0 的边,如果该点是岩石,则再连 流量为 1,费用为 1 的边
s 连 (1,1) 的入点, 流量为 k,费用为 0
(n,m) 的出点连 t 的出点,流量为 k, 费用 0
最后要输出方案,从 s 走到 t 走最大流的个数次,每次从出点遍历边,当该边的反向边流量不为 1 时,则从该边去走,同时将该反向边的流量减 1
#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
#define dbg(x...) do { cout << "\033[32;1m" << #x <<" -> "; err(x); } while (0)
void err() { cout << "\033[39;0m" << endl; }
template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); }
const int N = 2500 + 5;
const int M = 1000010;
int head[N], ver[M], edge[M], cost[M], nxt[M];
int d[N], incf[N], pre[N], v[N];
int n, m, k, tot, s, t, maxflow, ans, sum;
int a[50][50];
inline int id(int x, int y){return x * m + y;}
void add(int x, int y, int z, int c){
ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot, cost[tot] = c;
ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot, cost[tot] = -c;
}
bool spfa(){
queue<int> q;
memset(d, 0xcf, sizeof d);
memset(v, 0, sizeof v);
q.push(s);
d[s] = 0, v[s] = 1;
incf[s] = inf;
while(q.size()){
int x = q.front();
q.pop();
v[x] = 0;
for(int i=head[x];i;i=nxt[i]){
if(!edge[i]) continue;
int y = ver[i];
if(d[y] < d[x] + cost[i]){
d[y] = d[x] + cost[i];
incf[y] = min(incf[x], edge[i]);
pre[y] = i;
if(!v[y]) v[y] = 1, q.push(y);
}
}
}
if(d[t] == 0xcfcfcfcf)return false;
return true;
}
void update(){
int x = t;
while(x != s){
int i = pre[x];
edge[i] -= incf[t];
edge[i^1] += incf[t];
x = ver[i ^ 1];
}
maxflow += incf[t];
ans += incf[t] * d[t];
}
void print(int cur){
int x = id(0,0) + sum;
while(x != id(n-1, m-1) + sum){
int y;
for(int i=head[x];i;i=nxt[i]){
if(edge[i^1]){
edge[i^1]--;
y = ver[i];
break;
}
}
if(y == x - sum + 1){ // 向右走
printf("%d %d\n", cur, 1);
}
else printf("%d %d\n", cur, 0);
x = y + sum;
}
}
int main() {
scanf("%d%d%d",&k,&m,&n);
tot = 1, s = 2 * n * m + 1, t = s + 1;
sum = n * m;
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
scanf("%d",&a[i][j]);
if(a[i][j] == 0 || a[i][j] == 2) add(id(i,j), id(i,j) + sum, inf, 0);
if(a[i][j] == 2) add(id(i,j), id(i,j) + sum, 1, 1);
}
}
add(s, id(0, 0), k, 0);
add(id(n-1, m-1) + sum, t, k, 0);
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
if(i < n-1) add(id(i,j)+sum, id(i+1,j), inf, 0);
if(j < m-1) add(id(i,j)+sum, id(i,j+1), inf, 0);
}
}
while(spfa()) update();
//cout << maxflow << endl;
for(int i=1;i<=maxflow;i++){
print(i);
}
return 0;
}
深海机器人问题
这个题建图稍微有些复杂,需要理清楚当前输入的哪条边,只要仔细建好图,这个题就是不难的。(给出的起点和终点坐标不只是反的还是正的,就直接面向数据编程了)
#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
#define dbg(x...) do { cout << "\033[32;1m" << #x <<" -> "; err(x); } while (0)
void err() { cout << "\033[39;0m" << endl; }
template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); }
const int N = 1000 + 5;
const int M = 1000010;
int P, Q, A, B;
int ver[M], edge[M], cost[M], nxt[M], head[N];
int d[N], incf[N], pre[N], v[N];
int n, k, tot, s, t, maxflow, ans;
void add(int x,int y,int z,int c){
//dbg(x,y,z,c);
ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot, cost[tot] = c;
ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot, cost[tot] = -c;
}
inline int num(int i, int j, int k) { return (i - 1) * n + j + k * n * n; }
bool spfa(){
queue<int> q;
memset(d, 0xcf, sizeof d);
memset(v, 0, sizeof v);
q.push(s);
d[s] = 0, v[s] = 1;
incf[s] = inf;
while(q.size()){
int x = q.front();
q.pop();
v[x] = 0;
for (int i = head[x]; i;i=nxt[i]){
if(!edge[i])
continue;
int y = ver[i];
if(d[y] < d[x] + cost[i]){
d[y] = d[x] + cost[i];
incf[y] = min(incf[x], edge[i]);
pre[y] = i;
if(!v[y])
v[y] = 1, q.push(y);
}
}
}
if(d[t] == 0xcfcfcfcf)
return false;
return true;
}
void update(){
int x = t;
while(x != s){
int i = pre[x];
edge[i] -= incf[t];
edge[i ^ 1] += incf[t];
x = ver[i ^ 1];
}
maxflow += incf[t];
ans += d[t] * incf[t];
}
int id(int x, int y){
return x * (Q+1) + y;
}
int main() {
scanf("%d%d%d%d",&A,&B, &P, &Q);
tot = 1;
s = (P+1)*(Q+1), t = s + 1;
for(int i=0;i<P+1;i++){
for(int j=0;j<Q;j++){
int x;scanf("%d",&x);
add(id(i, j), id(i, j+1), 1, x);
add(id(i, j), id(i, j+1), inf, 0);
}
}
for(int j=0;j<Q+1;j++){
for(int i=0;i<P;i++){
int x;scanf("%d",&x);
add(id(i,j), id(i+1,j), 1, x);
add(id(i,j), id(i+1,j), inf, 0);
}
}
for(int i=1;i<=A;i++){
int k, x, y;
scanf("%d%d%d",&k,&x,&y);
//swap(x,y);
add(s, id(x, y), k, 0);
}
for(int i=1;i<=B;i++){
int k, x, y;
scanf("%d%d%d",&k,&x,&y);
//swap(x,y);
add(id(x,y), t, k, 0);
}
while(spfa())update();
printf("%d\n",ans);
return 0;
}
航空路线问题
这个题可以理解成从s 到 t 找两个不交叉的最长路径,拆点,x 向 x' 连流量为1,费用为1的边,s 向 s' 连流量为2,费用为0的边,t 向 t' 连流量为2,费用为0的边,原图的边(u,v),从 u' 向 v 连流量为1,费用为0的边, 注意一点的是如果 u = s, v = t, 则该边流量要设置为 2。从 s 到 t跑最大费用最大流, 然后输出方案即可。
#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
#include <unordered_map>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
#define dbg(x...) do { cout << "\033[32;1m" << #x <<" -> "; err(x); } while (0)
void err() { cout << "\033[39;0m" << endl; }
template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); }
const int N = 200 + 5;
const int M = 200010;
unordered_map<string,int> mp;
int ver[M], edge[M], cost[M], nxt[M], head[N];
int d[N], incf[N], pre[N], v[N];
int n, m, k, tot, s, t, maxflow, ans;
string name[101];
void add(int x,int y,int z,int c){
ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot, cost[tot] = c;
ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot, cost[tot] = -c;
}
inline int num(int i, int j, int k) { return (i - 1) * n + j + k * n * n; }
bool spfa(){
queue<int> q;
memset(d, 0xcf, sizeof d);
memset(v, 0, sizeof v);
q.push(s);
d[s] = 0, v[s] = 1;
incf[s] = inf;
while(q.size()){
int x = q.front();
q.pop();
v[x] = 0;
for (int i = head[x]; i;i=nxt[i]){
if(!edge[i])
continue;
int y = ver[i];
if(d[y] < d[x] + cost[i]){
d[y] = d[x] + cost[i];
incf[y] = min(incf[x], edge[i]);
pre[y] = i;
if(!v[y])
v[y] = 1, q.push(y);
}
}
}
if(d[t] == 0xcfcfcfcf)
return false;
return true;
}
void update(){
int x = t;
while(x != s){
int i = pre[x];
edge[i] -= incf[t];
edge[i ^ 1] += incf[t];
x = ver[i ^ 1];
}
maxflow += incf[t];
ans += d[t] * incf[t];
}
int main() {
scanf("%d%d",&n,&m);
tot = 1, s = 2 * n + 1, t = s + 1;
add(s, 1+n, 2, 0);
add(n, t, 2, 0);
for(int i=1;i<=n;i++){
cin >> name[i];
mp[name[i]] = i;
add(i, i+n, 1, 1);
}
for(int i=1;i<=m;i++){
string x,y;cin >> x >> y;
if(mp[x] > mp[y]) swap(x, y);
if(mp[x] == 1 && mp[y] == n)
add(mp[x] + n, mp[y], 2, 0);
else add(mp[x] + n, mp[y], 1, 0);
}
while(spfa()) update();
if(maxflow != 2){
puts("No Solution!");
return 0;
}
printf("%d\n", ans + 2);
cout << name[1] << endl;
memset(v, 0, sizeof v);
int p = 1 + n;
while(p != n+n){
for(int i=head[p];i;i=nxt[i]){
if(edge[i] == 0 && ver[i] <= n && !v[ver[i]]){
v[ver[i]] = 1;
p = ver[i];break;
}
}
cout << name[p] << endl;
p = p + n;
}
p = n;
while(p != 1){
for(int i=head[p];i;i=nxt[i]){
if(edge[i] != 0 && ver[i] <= 2 * n && !v[ver[i] - n]){
v[ver[i]-n] = 1;
p = ver[i] - n;break;
}
}
cout << name[p] << endl;
}
return 0;
}
数字梯形问题
#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
#define dbg(x...) do { cout << "\033[32;1m" << #x <<" -> "; err(x); } while (0)
void err() { cout << "\033[39;0m" << endl; }
template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); }
const int N = 1610;
const int M = 1000010;
int n, m, a[N][N], sum, id[N][N];
int ver[M], edge[M], cost[M], nxt[M], head[N];
int d[N], incf[N], pre[N], v[N];
int tot, s, t, maxflow, ans;
void add(int x,int y,int z,int c){
//dbg(x,y,z,c);
ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot, cost[tot] = c;
ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot, cost[tot] = -c;
}
bool spfa(){
queue<int> q;
memset(d, 0xcf, sizeof d);
memset(v, 0, sizeof v);
q.push(s);
d[s] = 0, v[s] = 1;
incf[s] = inf;
while(q.size()){
int x = q.front();
q.pop();
v[x] = 0;
for (int i = head[x]; i;i=nxt[i]){
if(!edge[i])
continue;
int y = ver[i];
if(d[y] < d[x] + cost[i]){
d[y] = d[x] + cost[i];
incf[y] = min(incf[x], edge[i]);
pre[y] = i;
if(!v[y])
v[y] = 1, q.push(y);
}
}
}
//cout << d[t] << endl;
if(d[t] == 0xcfcfcfcf)
return false;
return true;
}
void update(){
int x = t;
while(x != s){
int i = pre[x];
edge[i] -= incf[t];
edge[i ^ 1] += incf[t];
x = ver[i ^ 1];
}
maxflow += incf[t];
ans += d[t] * incf[t];
}
void clear(){
memset(head, 0, sizeof head);
maxflow = 0;
tot = 1;
}
void solve1(){
clear();
s = 2 * sum + 1, t = s + 1;
for(int i=1;i<=n;i++){
for(int j = 1; j <= m + i - 1; j++){
if(i == 1) add(s, id[i][j], 1, 0);
add(id[i][j], id[i][j]+sum, 1, a[i][j]);
if(i < n){
add(id[i][j] + sum, id[i+1][j], 1, 0);
add(id[i][j] + sum, id[i+1][j+1], 1, 0);
}else add(id[i][j]+sum, t, 1, 0);
}
}
//for(int i=1;i<=m+n-1;i++) add(id[n][i] + sum, t, 1, 0);
ans = 0;
while(spfa()) update();
printf("%d\n",ans);
}
void solve2(){
clear();
s = sum + 1, t = s + 1;
for(int i=1;i<=n;i++){
for(int j=1;j<=m+i-1;j++){
if(i == 1) add(s, id[i][j], 1, a[i][j]);
if(i < n) {
add(id[i][j], id[i+1][j], 1, a[i+1][j]);
add(id[i][j], id[i+1][j+1], 1, a[i+1][j+1]);
} else {
add(id[i][j], t, inf, 0);
}
}
}
ans = 0;
while(spfa()) update();
printf("%d\n",ans);
}
void solve3(){
clear();
s = sum + 1, t = s + 1;
for(int i=1;i<=n;i++){
for(int j=1;j<=m+i-1;j++){
if(i == 1) add(s, id[i][j], 1, a[i][j]);
if(i < n) {
add(id[i][j], id[i+1][j], inf, a[i+1][j]);
add(id[i][j], id[i+1][j+1], inf, a[i+1][j+1]);
} else {
add(id[i][j], t, inf, 0);
}
}
}
ans = 0;
while(spfa()) update();
printf("%d\n",ans);
}
int main() {
scanf("%d%d",&m,&n);
for(int i=1;i<=n;i++){
for(int j=1;j<=m+i-1;j++){
scanf("%d",&a[i][j]);
id[i][j] = ++sum;
}
}
solve1();
solve2();
solve3();
return 0;
}
骑士共存问题
互斥的位置,横坐标与纵坐标和的奇偶性不同,所以最终连接好的是一个二分图,将互斥关系建图,跑最大流求出最小割,含义是需要割掉多少条边使得不互斥,能割掉的只有从 s, 和 t 连接的边,所以把互斥关系的边流量设置为 inf,这样就可以表示要最多取消放置多少个骑士,使得图中不存在互斥关系。
#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
#define dbg(x...) do { cout << "\033[32;1m" << #x <<" -> "; err(x); } while (0)
void err() { cout << "\033[39;0m" << endl; }
template<class T, class... Ts> void err(const T& arg,const Ts&... args) { cout << arg << " "; err(args...); }
const int inf = 0x3f3f3f3f;
const int N = 40000 + 5;
const int M = 1000010;
int n, m, a[202][202];
int s, t, head[N], ver[M], nxt[M], edge[M], tot;
int d[N],maxflow;
void add(int x, int y, int z){
ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot;
ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot;
}
int dx[8] = {-1,-2,-2,-1,1,2,2,1};
int dy[8] = {-2,-1,1,2,2,1,-1,-2};
int id(int x, int y){return (x-1)*n + y;}
void add(int x, int y){
if(a[x][y])return;
if((x+y) & 1){
add(id(x,y), t, 1);
return ;
}
add(s, id(x, y), 1);
for(int i=0;i<8;i++){
int nx = x + dx[i], ny = y + dy[i];
if(nx < 1 || nx > n || ny < 1 || ny > n || a[nx][ny])continue;
add(id(x,y), id(nx,ny), 1);
}
}
bool bfs(){
queue<int> q;
memset(d,0,sizeof d);
q.push(s), d[s] = 1;
while(q.size()){
int x = q.front();q.pop();
for(int i=head[x];i;i=nxt[i]){
if(edge[i] && !d[ver[i]]){
d[ver[i]] = d[x] + 1;
if(ver[i] == t)return true;
q.push(ver[i]);
}
}
}
return false;
}
int dinic(int x, int flow){
if(x == t)return flow;
int rest = flow, k;
for(int i=head[x];i&&rest;i=nxt[i]){
if(edge[i] && d[ver[i]] == d[x] +1){
k = dinic(ver[i], min(edge[i], rest));
if(!k) d[ver[i]] = 0;
edge[i] -= k;
edge[i ^ 1] += k;
rest -= k;
}
}
return flow - rest;
}
int main() {
scanf("%d%d",&n,&m);
int sum = n * n;
for(int i=1;i<=m;i++){
int x,y;scanf("%d%d",&x,&y);
a[x][y] = 1;
sum --;
}
tot = 1, s = n * n + 1, t = s + 1;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
add(i, j);
}
}
int flow;
while(bfs())while(flow=dinic(s, inf))maxflow+=flow;
sum -= maxflow;
printf("%d\n", sum);
return 0;
}
运输问题
供需关系建图,跑最小费用最大流即可
#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 200 + 5;
const int M = 100010;
int n,m,a[N], b[N];
int ver[M], edge[M], cost[M], nxt[M], head[N];
int d[N], incf[N], pre[N], v[N], e[N][N];
int k, tot, s, t, maxflow, ans;
void add(int x,int y,int z,int c){
ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot, cost[tot] = c;
ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot, cost[tot] = -c;
}
bool spfa(){
queue<int> q;
memset(d, 0x3f, sizeof d);
memset(v, 0, sizeof v);
q.push(s);
d[s] = 0, v[s] = 1;
incf[s] = inf;
while(q.size()){
int x = q.front();
q.pop();
v[x] = 0;
for (int i = head[x]; i;i=nxt[i]){
if(!edge[i])
continue;
int y = ver[i];
if(d[y] > d[x] + cost[i]){
d[y] = d[x] + cost[i];
incf[y] = min(incf[x], edge[i]);
pre[y] = i;
if(!v[y])
v[y] = 1, q.push(y);
}
}
}
if(d[t] == inf)
return false;
return true;
}
void update(){
int x = t;
while(x != s){
int i = pre[x];
edge[i] -= incf[t];
edge[i ^ 1] += incf[t];
x = ver[i ^ 1];
}
maxflow += incf[t];
ans += d[t] * incf[t];
}
int main() {
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=m;i++)scanf("%d",&b[i]);
s = n + m + 1, t = s + 1;
tot = 1;
for(int i=1;i<=n;i++){
add(s, i, a[i], 0);
for(int j=1,x;j<=m;j++){
scanf("%d",&e[i][j]);
add(i, j+n, inf, e[i][j]);
}
}
for(int i=1;i<=m;i++) add(i+n, t, b[i], 0);
while(spfa()) update();
printf("%d\n", ans);
memset(head, 0, sizeof head);
tot = 1;
for(int i=1;i<=n;i++){
add(s, i, a[i], 0);
for(int j=1,x;j<=m;j++){
add(i, j+n, inf, -e[i][j]);
}
}
for(int i=1;i<=m;i++) add(i+n, t, b[i], 0);
ans = 0;
while(spfa()) update();
printf("%d\n", -ans);
return 0;
}
汽车加油行驶问题
这是个最短路的题,d[x][y][i]
表示从起点到 (x,y)
,油量为i
的最小花费。
注意一点的是如果到达了一个有加油站的地点,那么这时的操作必须要加油,加满了之后才能接着走。
#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 100 + 5;
int a[N][N], n, k, A, B, C;
int d[N][N][11], v[N][N][11];
struct node{
int x, y, k;
node(int x,int y, int k):x(x),y(y),k(k){}
};
bool operator <(const node a, const node b){
return a.x < b.x;
}
#define mk make_pair
priority_queue<pair<int,node>>q;
int dx[4] = {-1,0,1,0}, dy[4] = {0, -1, 0, 1};
int dijkstra(){
memset(d, 0x3f, sizeof d);
memset(v, 0, sizeof v);
q.push(mk(0, (node){1,1,k}));
d[1][1][k] = 0;
while(q.size()){
auto t = q.top().second;q.pop();
int x = t.x, y = t.y, ck = t.k;
if(v[x][y][ck])continue;
v[x][y][ck] = 1;
if(ck < k){
if(a[x][y] == 0){
if(d[x][y][k] > d[x][y][ck] + A + C){//如果这个地方没有加油站,可以建一个加油站
d[x][y][k] = d[x][y][ck] + A + C;
q.push(mk(-d[x][y][k], (node){x,y,k}));
}
}else{
if(d[x][y][k] > d[x][y][ck] + A){//加油操作
d[x][y][k] = d[x][y][ck] + A;
q.push(mk(-d[x][y][k], (node){x,y,k}));
}
continue;//这里要continue
}
}
if(ck)
for(int i=0;i<4;i++){
int nx = x + dx[i], ny = y + dy[i];
if(nx < 1 || nx > n || ny < 1 || ny > n)continue;
int cost = d[x][y][ck];
if(i < 2) cost += B;
if(d[nx][ny][ck-1] > cost){
d[nx][ny][ck-1] = cost;
q.push(mk(-d[nx][ny][ck-1], (node){nx,ny,ck-1}));
}
}
}
int res = inf;
for(int i=0;i<=k;i++) res = min(res, d[n][n][i]);
return res;
}
int main() {
scanf("%d%d%d%d%d",&n,&k,&A,&B,&C);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++)scanf("%d",&a[i][j]);
}
printf("%d\n", dijkstra());
return 0;
}
分配问题
二分图带权最大匹配问题,可以用费用流解决但是复杂度比较玄学,如果边数太多很可能会卡(\(M = N^2\)),边数在500以下时完全可以跑KM,但是最好用BFS版本的KM,复杂度为 \(O(n^3)\) , DFS版本的KM复杂度一般都是\(O(N^4)\)
可以参考https://www.cnblogs.com/1625--H/p/12468117.html 中的 J-spy
#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 100 + 5;
ll n, a[N],b[N],c[N],p[N];
ll w[N][N];
ll lx[N] , ly[N];
ll match[N];
ll slack[N];
bool vy[N];
ll pre[N];
void bfs( ll k ){
ll x , y = 0 , yy = 0 , delta;
memset( pre , 0 , sizeof(pre) );
for( ll i = 1 ; i <= n ; i++ ) slack[i] = inf;
match[y] = k;
while(1){
x = match[y]; delta = inf; vy[y] = true;
for( ll i = 1 ; i <= n ;i++ ){
if( !vy[i] ){
if( slack[i] > lx[x] + ly[i] - w[x][i] ){
slack[i] = lx[x] + ly[i] - w[x][i];
pre[i] = y;
}
if( slack[i] < delta ) delta = slack[i] , yy = i ;
}
}
for( ll i = 0 ; i <= n ; i++ ){
if( vy[i] ) lx[match[i]] -= delta , ly[i] += delta;
else slack[i] -= delta;
}
y = yy ;
if( match[y] == -1 ) break;
}
while( y ) match[y] = match[pre[y]] , y = pre[y];
}
ll KM(){
memset( lx , 0 ,sizeof(lx) );
memset( ly , 0 ,sizeof(ly) );
memset( match , -1, sizeof(match) );
for( ll i = 1 ; i <= n ; i++ ){
memset( vy , false , sizeof(vy) );
bfs(i);
}
ll res = 0 ;
for( ll i = 1 ; i <= n ; i++ ){
if( match[i] != -1 ){
res += w[match[i]][i] ;
}
}
return res;
}
int main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)scanf("%lld",&w[i][j]);
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)w[i][j] = -w[i][j];
printf("%lld\n",-KM());
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)w[i][j] = -w[i][j];
printf("%lld\n",KM());
return 0;
}
魔术球问题
可以想到最终答案不会特别大,因为完全平方数到后面会非常稀疏
所以每次新增一个球,将它与之前可以组合为完全平方数的球连边,在残余网络中跑最大流,当球数量 - 最大流 > n 时,就停止
#include <cstdio>
#include <iostream>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int N = 20000 + 5;
const int maxn = 10000;
const int M = 200010;
int head[N], ver[M], edge[M], nxt[M], d[N];
int vis[N];
int n, m, s, t, tot, maxflow, res;
queue<int> q;
void add(int x, int y, int z){
ver[++tot] = y, edge[tot] = z, nxt[tot] = head[x], head[x] = tot;
ver[++tot] = x, edge[tot] = 0, nxt[tot] = head[y], head[y] = tot;
}
bool bfs(){
memset(d, 0, sizeof d);
while(q.size())q.pop();
q.push(s);d[s] = 1;
while(q.size()){
int x = q.front();q.pop();
for(int i=head[x];i;i=nxt[i]){
if(edge[i] && !d[ver[i]]){
q.push(ver[i]);
d[ver[i]] = d[x] + 1;
if(ver[i] == t) return 1;
}
}
}
return 0;
}
int dinic(int x, int flow){
if(x == t) return flow;
int rest = flow, k;
for(int i=head[x];i && rest; i=nxt[i]){
if(edge[i] && d[ver[i]] == d[x] + 1){
k = dinic(ver[i], min(rest, edge[i]));
if(!k) d[ver[i]] = 0;
edge[i] -= k;
edge[i ^ 1] += k;
rest -= k;
}
}
return flow - rest;
}
bool isok(int x){
int s = sqrt(x);
return s * s == x;
}
void get(int x){
int t = x;
bool flag;
do{
vis[t] = 1;printf("%d ", t);
flag = false;
for(int i=head[t];i;i=nxt[i]){
if(ver[i] == s || vis[i] == res || edge[i])continue;
flag = true;t = ver[i]-maxn;break;
}
}while(flag);
puts("");
}
int main() {
scanf("%d",&n);
s = 0, t = 20000;
tot = 1;
for(int i=1;;i++){
add(s, i, 1); add(i+maxn, t, 1);
for(int j=1;j<i;j++){
if(isok(i+j)) add(j, i + maxn, 1);
}
int flow = 0;
while(bfs())while(flow = dinic(s, inf)) maxflow += flow;
if(i - maxflow > n){
res = i - 1;break;
}
}
printf("%d\n",res);
for(int i=1;i<=res;i++){
if(!vis[i])get(i);
}
return 0;
}