网络流24题 总结
题目按照\(LOJ\)顺序了
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
17 18 19 20
21 22 23
只有\(23\)个 真好
\(1.\) 搭配飞行员
所有飞行员分成正副驾驶员两类而且相同类之间没有边
直接一个配对完事 对应的正副飞行员之间连边就可以跑二分图
加两个边就是网络流
注\(:\) 二分图 $ \Leftrightarrow$ 网络流
左部图连源点 右部图连汇点 容量都是\(1\) 就完事了
\(Code:\)
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
using namespace std;
typedef long long ll;
ll read() {
ll as = 0,fu = 1;
char c = getchar();
while(c < '0' || c > '9') {
if(c == '-') fu = -1;
c = getchar();
}
while(c >= '0' && c <= '9') {
as = as * 10 + c - '0';
c = getchar();
}
return as * fu;
}
const int N = 200005;
//head
int n,m,ans;
int head[105],mo[N],cst[N],nxt[N],cnt;
inline void add(int x,int y) {
mo[++cnt] = y;
nxt[cnt] = head[x];
head[x] = cnt;
mo[++cnt] = x;
nxt[cnt] = head[y];
head[y] = cnt;
return;
}
int vis[105],match[105];
bool dfs(int x,int tm) {
for(int i = head[x]; i; i = nxt[i]) {
int sn = mo[i];
if(vis[sn] ^ tm) {
vis[sn] = tm;
if(!match[sn] || dfs(match[sn],tm)) {
match[sn] = x;
return 1;
}
}
}
return 0;
}
int main() {
m = read();
n = read();
while(1) {
int a = read();
int b = read();
if(a < 0 && b < 0) break;
add(a,b);
}
rep(i,1,m) ans += dfs(i,i);
printf("%d\n",ans);
rep(i,1,n) match[match[i]] = i;
rep(i,1,m) if(match[i]) printf("%d %d\n",i,match[i]);
return 0;
}
\(2.\) 太空飞行计划
坑在读入和输出上...
如果看过我以前的blog应该是讲过最大权闭合图的
这个地方就是最大权闭合图 然后求最小割减掉
具体建图就是
实验连\(S\) 容量是收入
器材连\(T\) 容量是支出
实验和对应的器材连边 容量\(inf\)
至于输出方案就看那些边没有被割掉
从源点开始搜残留网络 给在残留网络中的节点打一个\(vis\)标记
最后输出有\(vis\)的就行啦
\(Code:\)
这边代码是洛咕能A的 \(LOJ\)需要去掉行尾space
#include<cmath>
#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define rep(i,a,n) for(int i = a;i <= n;++i)
#define per(i,n,a) for(int i = n;i >= a;--i)
#define inf 2147483647
#define ms(a,b) memset(a,b,sizeof a)
using namespace std;
typedef long long ll;
ll read() {
ll as = 0,fu = 1;
char c = getchar();
while(c < '0' || c > '9') {
if(c == '-') fu = -1;
c = getchar();
}
while(c >= '0' && c <= '9') {
as = as * 10 + c - '0';
c = getchar();
}
return as * fu;
}
const int N = 200005;
const int M = 154;
//head
int S = N-1,T = N-2;
int head[N],nxt[N<<1],cst[N<<1],tp[N<<1],cnt = 1;
void _add(int x,int y,int w) {
tp[++cnt] = y;
nxt[cnt] = head[x];
cst[cnt] = w;
head[x] = cnt;
}
void add(int x,int y,int w) {
_add(x,y,w),_add(y,x,0);
}
int cur[N];
int dep[N];
bool bfs(int S,int T) {
queue<int> q;
memcpy(cur,head,sizeof cur);
ms(dep,0),dep[S] = 1;
q.push(S);
while(!q.empty()) {
int x = q.front();
q.pop();
for(int i = head[x];i;i = nxt[i]) {
int sn = tp[i];
if(!dep[sn] && cst[i]) {
dep[sn] = dep[x] + 1;
q.push(sn);
}
}
}
return dep[T];
}
int dfs(int x,int lim,int T) {
if(x == T || !lim) return lim;
int res = 0;
for(int &i = cur[x];i;i = nxt[i]) {
int sn = tp[i];
if(dep[sn] == dep[x] + 1 && cst[i]) {
int d = dfs(sn,min(cst[i],lim),T);
cst[i] -= d,cst[i^1] += d;
lim -= d,res += d;
if(!lim) break;
}
}
return res;
}
int DINIC(int S,int T) {int res = 0;while(bfs(S,T)) res += dfs(S,inf,T);return res;}
///////////////////////////////////////////////////////////////////////////////////
int n,m;
int p[M],r[M];
vector<int> v[M];
bool vis[M];
void dfswy(int x,int f) {
for(int i = head[x];i;i = nxt[i]) {
int sn = tp[i];
if(!cst[i] || vis[sn]) continue;
vis[sn] = 1,dfswy(sn,x);
}
}
int main() {
// freopen("in.in","r",stdin);
scanf("%d%d",&m,&n);
rep(i,1,m) {
scanf("%d",p+i);
char tools[10000];
memset(tools,0,sizeof tools);
cin.getline(tools,10000);
int ulen=0,tool;
while (sscanf(tools+ulen,"%d",&tool)==1)//之前已经用scanf读完了赞助商同意支付该实验的费用
{//tool是该实验所需仪器的其中一个
v[i].push_back(tool);
//这一行,你可以将读进来的编号进行储存、处理,如连边。
if (tool==0)
ulen++;
else {
while (tool) {
tool/=10;
ulen++;
}
}
ulen++;
}
add(S,i,p[i]);
r[i] = cnt-1;
rep(j,0,v[i].size()-1) add(i,v[i][j]+m,inf);
}
int cst = 0;
rep(i,1,n) scanf("%d",&cst),add(i+m,T,cst);
cst = 0;
rep(i,1,m) cst += p[i];
cst -= DINIC(S,T);
dfswy(S,S);
rep(i,1,m) if(vis[i]) printf("%d ",i);puts("");
rep(i,1,n) if(vis[i+m]) printf("%d ",i);puts("");
printf("%d\n",cst);
return 0;
}
\(3.\) 最小路径覆盖
路径不相交... 拆点吧...
将原图的点拆成\((A,B)\)两部分
然后原图的边 $$(u,v)$$
改为 $$(B(u),A(v))$$
容量是\(1\) 就可以了
所有\(A\)连源点 \(B\)连汇点 容量\(inf\)
跑最小割就是路径条数(因为割掉的一定是所有的原图的边)
相当于求增广路径条数?
输出方案比较恶心可能是我菜
记录一下每个点在增广路上的前驱
没有前驱的就是路径起点 然后像前向星一样跳就行了
(话说\(24\)题难的好像就是输出方案)
\(Code:\)
#include<map>
#include<queue>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define rep(i,a,n) for(int i = a;i <= n;++i)
#define per(i,n,a) for(int i = n;i >= a;--i)
#define ms(a,b) memset(a,b,sizeof a)
#define inf 2147483647
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef double D;
ll read() {
ll as = 0,fu = 1;
char c = getchar();
while(c < '0' || c > '9') {
if(c == '-') fu = -1;
c = getchar();
}
while(c >= '0' && c <= '9') {
as = as * 10 + c - '0';
c = getchar();
}
return as * fu;
}
const int N = 100005;
const int M = 305;
//head
int S = N - 1,T = N - 2;
int head[N],nxt[N<<1],tp[N<<1],cst[N<<1],cnt = 1;
void _add(int x,int y,int w) {
nxt[++cnt] = head[x],head[x] = cnt;
tp[cnt] = y,cst[cnt] = w;
}
void add(int x,int y,int w) {
_add(x,y,w),_add(y,x,0);
}
int cur[N],dep[N];
bool bfs(int S,int T) {
queue<int> q;
ms(dep,0),memcpy(cur,head,sizeof cur);
dep[S] = 1,q.push(S);
while(!q.empty()) {
int x = q.front();
q.pop();
for(int i = head[x];i;i = nxt[i]) {
int sn = tp[i];
if(!dep[sn] && cst[i]) dep[sn] = dep[x] + 1,q.push(sn);
}
}
return dep[T];
}
int n,m;
int jmp[N],tag[N];
int dfs(int x,int lim,int T) {
if(x == T || !lim) return lim;
int res = 0;
for(int &i = cur[x];i;i = nxt[i]) {
int sn = tp[i];
if(dep[sn] == dep[x] + 1 && cst[i]) {
int d = dfs(sn,min(cst[i],lim),T);
cst[i] -= d,cst[i^1] += d;
lim -= d,res += d;
if(d == 0 || x == S) continue;
// cout << x << ' ' << sn-n << endl;
tag[sn-n] = 1,jmp[x] = sn-n;
if(!lim) break;
}
}
return res;
}
int DINIC(int S,int T) {int res = 0;while(bfs(S,T)) res += dfs(S,inf,T);return res;}
/////////////////////////////////////////////////////////////////////////////////////
int r[N];
bool vis[N];
vector<int> v[N];
int main() {
// freopen("in.in","r",stdin);
n = read(),m = read();
rep(i,1,n) r[i] = cnt;
rep(i,1,m) {
int x = read(),y = read();
add(x,y+n,1);
}
rep(i,1,n) add(S,i,1),add(i+n,T,1);
int ans = DINIC(S,T);
rep(i,1,n) {
if(tag[i]) continue;
printf("%d ",i);
int x = i;
while(jmp[x] && jmp[x] ^ T)
x = jmp[x],printf("%d ",x);
puts("");
}
printf("%d\n",n - ans);
return 0;
}
\(4.\) 魔术球问题
这题贪心可以\(28ms\)过 吊打网络流\(50\)倍...
贪心就是现在能放在已有的柱子上就不开新的
首先脑补一下可以发现同一时刻最多只有\(1\)个柱子合法
然后如果有合法的又去开新的 后面循环的轮数最多不变 而且你的新柱子个数少了一个
或者说 比如你现在在凑\(a*a\) 然后 你突然能配成却不配
就算后面没有影响 你这个地方也少了一个 后面不会再补回来(因为配的平方不是\(a*a\))
贪心代码就很简单了(这个好像不是我的代码)
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
int n, cnt, ans=1, isa[3205];
vector<int> d[62];
int main(){
for(int i=1; i*i<=3205; i++)
isa[i*i] = true;
cin>>n;
while(1){
for(int i=1; i<=cnt; i++)
if(isa[d[i][d[i].size()-1]+ans]){
d[i].push_back(ans);
ans++;
i = 0;
continue;
}
if(cnt<n)
d[++cnt].push_back(ans++);
else break;
}
cout<<ans-1<<endl;
for(int i=1; i<=n; i++){
for(int j=0; j<d[i].size(); j++)
printf("%d ", d[i][j]);
printf("\n");
}
return 0;
}
然后讲网络流做法
(这题看起来像个匹配)
因为\(n \leq 55\) 答案不会超过\(10000\) (?
所以球之间连边没啥事
因为同一个球只能在一个球的上面 也只能在一个球的下面
所以拆点$$L(x),R(x)$$
能配成平方的\(i,j(i<j)\)之间连边$$(L(i),R(j))$$ 容量\(1\)
每个球连\((S,L(i)))\) 和 \((R(i),T)\) 容量\(1\)
(这题就是个匹配)
每次加进来一个新球就加上它作为\(j\)的边 \(DINIC\)一遍
如果流量是\(0\)就加柱子
求方案就在加柱子的时候记录一下第一个球是啥然后跑满流的边输出
注意:
\(1.\) 一开始T了 连边的时候可以手动剪枝 不要连出负的点编号来
\(2.\) 跑满流的时候要从\(L(i)\)开始跑
\(Code:\)
#include<cmath>
#include<ctime>
#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define By prophetB
#define rep(i,a,n) for(int i = a;i <= n;++i)
#define per(i,n,a) for(int i = n;i >= a;--i)
#define inf 2147483647
#define ms(a,b) memset(a,b,sizeof a)
using namespace std;
typedef double D;
typedef long long ll;
ll read() {
ll as = 0,fu = 1;
char c = getchar();
while(c < '0' || c > '9') {
if(c == '-') fu = 0;
c = getchar();
}
while(c >= '0' && c <= '9') {
as = as * 10 + c - '0';
c = getchar();
}
return fu ? as : -as;
}
const int N = 400005;
const int M = 154;
//head
int S = N-1,T = N-2;
int head[N],nxt[N<<1],cst[N<<1],tp[N<<1],cnt = 1;
void _add(int x,int y,int w) {
tp[++cnt] = y;
nxt[cnt] = head[x];
cst[cnt] = w;
head[x] = cnt;
}
void add(int x,int y,int w) {
_add(x,y,w),_add(y,x,0);
}
int cur[N];
int dep[N];
bool bfs(int S,int T) {
queue<int> q;
memcpy(cur,head,sizeof cur);
ms(dep,0),dep[S] = 1;
q.push(S);
while(!q.empty()) {
int x = q.front();
q.pop();
for(int i = head[x];i;i = nxt[i]) {
int sn = tp[i];
if(!dep[sn] && cst[i]) {
dep[sn] = dep[x] + 1;
q.push(sn);
}
}
}
return dep[T];
}
int dfs(int x,int lim,int T) {
if(x == T || !lim) return lim;
int res = 0;
for(int &i = cur[x];i;i = nxt[i]) {
int sn = tp[i];
if(dep[sn] == dep[x] + 1 && cst[i]) {
int d = dfs(sn,min(cst[i],lim),T);
if(!d) continue;
cst[i] -= d,cst[i^1] += d;
lim -= d,res += d;
if(!lim) break;
}
}
return res;
}
int DINIC(int S,int T) {int res = 0;while(bfs(S,T)) res += dfs(S,inf,T);return res;}
///////////////////////////////////////////////////////////////////////////////////
int n,ans;
#define L(x) (x<<1)
#define R(x) (x<<1|1)
int stk[N];
void output(int x) {
for(int i = head[x];i;i = nxt[i]) {
int sn = tp[i];
if(sn == S || sn == T || cst[i]) continue;
printf("%d ",sn>>=1);
output(sn<<1);
}
}
int main() {
// freopen("in.in","r",stdin);
D B = clock();
n = read();
while(++ans) {
add(S,L(ans),1),add(R(ans),T,1);
rep(i,1,sqrt(ans<<1)+1)
if(i * i > ans && i * i < (ans<<1)) add(L(i*i-ans),R(ans),1);
int res = DINIC(S,T);
if(res == 0) stk[++stk[0]] = ans;
if(stk[0] == n+1) break;
}
stk[0]--,ans--;
printf("%d\n",ans);
rep(i,1,stk[0]) {
printf("%d ",stk[i]);
output(stk[i]<<1);
puts("");
}
D E = clock();
// printf("%.3lf\n",(E - B) / CLOCKS_PER_SEC);
return 0;
}