飞行员配对
(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
",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
",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 - 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
", mx);
printf("%d
", 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
", 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
",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
",sum);
return 0;
}
负载均衡问题
裸的均分纸牌问题,可以(O(nlog n)) 时间解决
网络流的可以这样考虑,设平均值为 (ave)
对于权值大于等于 (ave) 的点,连接 (s ightarrow i) ,边权为 (a[i] -ave) ,费用(0)
对于权值小于等于(ave) 的点,连接(i ightarrow t) , 边权为(ave-a[i]), 费用 (0) ,
对于相邻的点:连接(i ightarrow 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
",d[t]);
return 0;
}
孤岛营救问题
(e[x1][y1][x2][y2]) 表示((x1,y1) ightarrow (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)
",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
",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
", 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
",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 << "