【网络流杂谈】
简介:
快要省选了,现在啥也不会的菜鸡最后的挣扎
这个星期给自己的任务是搞懂网络流
这个星期的东西都更在这个贴里了
题目:
一:[SCOI2007]蜥蜴
思路:
考虑建图,主要是将一个点拆成两个点,入点和出点间连边,这条边的权值可以限制这条边的走的次数
代码:
#include<iostream>
#include<cstdio>
#include<queue>
#include<cmath>
#include<cstring>
#define ll long long
int r,c,d;
int h[50][50],f[50][50];
int head[100000];
struct P{
int to,next,v;
}e[100000];
int s,t;
int cnt = -1;
void add(ll x,ll y,ll v){
e[++cnt].to = y;
e[cnt].next = head[x];
e[cnt].v = v;
head[x] = cnt;
}
double get(ll x1,ll y1,ll x2,ll y2){
return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}
int sum;
int vis[10000];
bool bfs(){
std::queue<int>q;
q.push(s);
std::memset(vis,-1,sizeof(vis));
vis[s] = 1;
while(!q.empty()){
int u = q.front();
q.pop();
for(int i = head[u];i != -1;i = e[i].next){
int v = e[i].to;
if(e[i].v && vis[v] == -1){
vis[v] = vis[u] + 1;
q.push(v);
}
}
}
return vis[t] != -1;
}
int dfs(int u,int flow){
if(u == t) return flow;
int used = 0,w;
for(int i = head[u];i != -1;i = e[i].next){
int v = e[i].to;
if(vis[v] == vis[u] + 1 && e[i].v){
w = dfs(v,std::min(flow - used,e[i].v));
e[i].v -= w;
e[i ^ 1].v += w;
used += w;
if(used == flow) return used;
}
}
if(!used)vis[u] = -1;
return used;
}
int dinic(){
int ans = 0;
while(bfs())ans += dfs(s,0x3f3f3f3f);
return ans;
}
int main(){
scanf("%d%d%d",&r,&c,&d);
memset(head,-1,sizeof(head));
for(int i = 1;i <= r;++i)
for(int j = 1;j <= c;++j)
scanf("%1d",&h[i][j]);
s = 0,t = 2 * r * c + 1;
for(int i = 1;i <= r;++i)
for(int j = 1;j <= c;++j){
char a = getchar();
while(a != '.' && a != 'L'){
a = getchar();
}
if(a == 'L')
f[i][j] = 1;
}
int now = 0;
for(int i = 1;i <= r;++i)
for(int j = 1;j <= c;++j){
++now;
if(h[i][j]){
add(now,now + r * c,h[i][j]);
add(now + r * c,now,0);
}
if(f[i][j]){
add(s,now,1);
add(now,s,0);
sum ++ ;
}
if((i <= d || i >= r - d + 1) && h[i][j]){
add(now + r * c,t,0x3f3f3f3f);
add(t,now + r * c,0);
}else
if((j <= d || j >= c - d + 1 )&& h[i][j]){
add(now + r * c,t,0x3f3f3f3f);
add(t,now + r * c,0);
}
}
for(int i = 1;i <= r;++i)
for(int j = 1;j <= c;++j)
for(int p = 1;p <= r;++p)
for(int q = 1;q <= c;++q){
if(get(i,j,p,q) <= (double)d && (i != p || j != q) && h[i][j] && h[p][q]){
add((i - 1) * c + j + r * c,(p - 1) * c + q,0x3f3f3f3f);
add((p - 1) * c + q,(i - 1) * c + j + r * c,0);
}
}
std::cout<<sum - dinic()<<std::endl;
}//调了很久,果然我是菜得不行
二: [ICPC-Beijing 2006]狼抓兔子
思路:说起来还是蛮有感触的
想当年第一次打开\(BZOJ\)看到的就是这个题,当时什么也不会就搜了题解,不过什么也没看懂
当时那个懵懂的人,现在却在为省选准备啊
很明显是最小割,不过也有平面图对偶图最短路路的做法
平面图对偶图:
如果一个图是 \(G\) 平面图,那么其存在一个对偶图 \(G′\) ,构造方法为将原图中的每个面替换成点,点替换为面,新的边与原来的边分别相交。
那么此时有,原图的最大流 = 最小割 = 对偶图最短路。
代码:
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#define ll long long
int n,m;
int id(int x,int y){return (x - 1) * m + y;}
int head[1000005];
struct P{
int to,next,v;
}e[6000005];
int s,t;
int cnt = 1;
void add(ll x,ll y,ll v){
e[++cnt].to = y;
e[cnt].next = head[x];
e[cnt].v = v;
head[x] = cnt;
}
int sum;
int vis[1000005];
bool bfs(){
std::queue<int>q;
q.push(s);
std::memset(vis,-1,sizeof(vis));
vis[s] = 1;
while(!q.empty()){
int u = q.front();
q.pop();
for(int i = head[u];i;i = e[i].next){
int v = e[i].to;
if(e[i].v && vis[v] == -1){
vis[v] = vis[u] + 1;
q.push(v);
}
}
}
return vis[t] != -1;
}
int dfs(int u,int flow){
if(u == t) return flow;
int used = 0,w;
for(int i = head[u];i;i = e[i].next){
int v = e[i].to;
if(vis[v] == vis[u] + 1 && e[i].v){
w = dfs(v,std::min(flow - used,e[i].v));
e[i].v -= w;
e[i ^ 1].v += w;
used += w;
if(used == flow) return used;
}
}
if(!used)vis[u] = -1;
return used;
}
int dinic(){
int ans = 0;
while(bfs())ans += dfs(s,0x3f3f3f3f);
return ans;
}
int main(){
scanf("%d%d",&n,&m);
s = 1,t = id(n,m);
for(int i = 1;i <= n;++i)
for(int j = 1;j <= m - 1;++j){
ll k;
scanf("%lld",&k);
add(id(i,j),id(i,j + 1),k);
add(id(i,j + 1),id(i,j),k);
}
for(int i = 1;i <= n - 1;++i)
for(int j = 1;j <= m;++j){
ll k;
scanf("%lld",&k);
add(id(i,j),id(i + 1,j),k);
add(id(i + 1,j),id(i,j),k);
}
for(int i = 1;i <= n - 1;++i)
for(int j = 1;j <= m - 1;++j){
ll k;
scanf("%lld",&k);
add(id(i,j),id(i + 1,j + 1),k);
add(id(i + 1,j + 1),id(i,j),k);
}
std::cout<<dinic();
}
最小费用最大流
把最开始的FF算法的\(bfs\)部分改为\(spfa\),同时记录前缀边,方便进行数据更新(基于\(dinic\)的最小费用流算法过几天再写)
代码
#include<iostream>
#include<cstdio>
#include<queue>
#define ll long long
int n,m,s,t;
struct P{
int to,next,v,c;
}e[100005];
int head[5005],minw[5005],minc[5005],pre[5005];
bool vis[5005];
int cnt = 1;
void add(int x,int y,int w,int c){
e[++cnt].to = y;
e[cnt].next = head[x];
e[cnt].v = w;
e[cnt].c = c;
head[x] = cnt;
}
bool spfa(){
std::queue<int>QWQ;
for(int i = 1;i <= n;++i){
minc[i] = 0x3f3f3f3f,vis[i] = 0;
}
QWQ.push(s);
vis[s] = 1;
minw[s] = 0x3f3f3f3f;
minc[s] = 0;
while(!QWQ.empty()){
int now = QWQ.front();
QWQ.pop();
vis[now] = 0;
for(int i = head[now];i;i = e[i].next){
if(e[i].v){
int v = e[i].to;
if(minc[v] > minc[now] + e[i].c){
minc[v] = minc[now] + e[i].c;
pre[v] = i;
minw[v] = std::min(minw[now],e[i].v);
if(!vis[v])vis[v] = 1,QWQ.push(v);
}
}
}
}
return minc[t] != 0x3f3f3f3f;
}
int answ,ansc;
void up(){
int now = t;
answ += minw[t];
ansc += minc[t] * minw[t];
while(now != s){
e[pre[now]].v -= minw[t];
e[pre[now] ^ 1].v += minw[t];
now = e[pre[now] ^ 1].to;
}
}
int main(){
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i = 1;i <= m;++i){
int u,v,w,c;
scanf("%d%d%d%d",&u,&v,&w,&c);
add(u,v,w,c);
add(v,u,0,-c);
}
while(spfa())
up();
std::cout<<answ<<" "<<ansc<<std::endl;
}//明白自己深深的弱小
一: [SDOI2013]费用流
[SDOI2013]费用流
还是一样拆点来满足题目要求的次数限制,在拆开的两点间的费用应为\(0\)
代码
#include<iostream>
#include<cstdio>
#include<queue>
#define ll long long
int n,m,s,t;
struct P{
int to,next,v,c;
}e[100005];
int head[5005],minw[5005],minc[5005],pre[5005];
bool vis[5005];
int cnt = 1;
void add(int x,int y,int w,int c){
e[++cnt].to = y;
e[cnt].next = head[x];
e[cnt].v = w;
e[cnt].c = c;
head[x] = cnt;
}
bool spfa(){
std::queue<int>QWQ;
for(int i = 1;i <= 2 * n;++i){
minc[i] = 0x3f3f3f3f,vis[i] = 0;
}
QWQ.push(s);
vis[s] = 1;
minc[s] = 0;
minw[s] = 0x3f3f3f3f;
while(!QWQ.empty()){
int now = QWQ.front();
QWQ.pop();
vis[now] = 0;
for(int i = head[now];i;i = e[i].next){
int v = e[i].to;
if(e[i].v){
if(minc[v] > minc[now] + e[i].c){
minc[v] = minc[now] + e[i].c;
pre[v] = i;
minw[v] = std::min(minw[now],e[i].v);
if(!vis[v])vis[v] = 1,QWQ.push(v);
}
}
}
}
return minc[t] != 0x3f3f3f3f;
}
int answ,ansc;
void up(){
answ += minw[t];
ansc += minc[t] * minw[t];
int now = t;
while(now != s){
e[pre[now]].v -= minw[t];
e[pre[now] ^ 1].v += minw[t];
now = e[pre[now] ^ 1].to;
}
}
int main(){
scanf("%d%d",&n,&m);
s = n + 1,t = n;
for(int i = 1;i <= n;++i){
add(i,i + n,1,0);
add(i + n,i,0,0);
}
for(int i = 1;i <= m;++i){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a + n,b,1,c);
add(b,a + n,0,-c);
}
while(spfa())
up();
std::cout<<answ<<" "<<ansc<<std::endl;
}//十分钟能敲完这些代码,手速还可以
文理分科
文理分科
一种典型的二选一的题目,可以将两个选择设为\(s,t\),然后分别连边,然后求最小割
对于题目中所说的集体选课多权值的情况,可以虚构\((i,j)'\)然后以\(ss[i][j]为权在他和s之间连边\),然后以\(inf\)在\((i,j)自己和周围五个点连边\),这步\(inf\)是为了防止这条边被割掉
然后求最小割,用全部的权值和减去最大流即可
#include<iostream>
#include<cstdio>
#include<queue>
#define ll long long
ll n,m;
ll a[200][200],s1[200][200],sa[200][200],ss[200][200];
int id(int x,int y){return (x - 1) * m + y;}
struct P{
int to,next,v;
}e[500000];
ll cnt = 1,head[500000];
ll s,t;
void add(ll x,ll y,ll v){
e[++cnt].to = y;
e[cnt].v = v;
e[cnt].next = head[x];
head[x] = cnt;
}
ll sum = 0;
ll vis[500000];
ll pcnt;
std::queue<ll>QWQ;
bool bfs(){
for(int i = 1;i <= pcnt;++i)
vis[i] = -1;
vis[s] = 1;
QWQ.push(s);
while(!QWQ.empty()){
int u = QWQ.front();
QWQ.pop();
for(int i = head[u];i;i = e[i].next){
int v = e[i].to;
if(e[i].v && vis[v] == -1){
vis[v] = vis[u] + 1;
QWQ.push(v);
}
}
}
return vis[t] != -1;
}
ll dfs(ll now,ll flow){
if(now == t)return flow;
ll used = 0;
for(int i = head[now];i;i = e[i].next){
ll v = e[i].to;
if(e[i].v && vis[v] == vis[now] + 1){
ll w = dfs(v,std::min(flow - used,(ll)e[i].v));
e[i].v -= w;
e[i ^ 1].v += w;
used += w;
if(flow == used)
break;
}
}
if(!used)vis[now] = -1;
return used;
}
ll dinic(){
ll ans = 0;
while(bfs())
ans += dfs(s,0x3f3f3f3f);
return ans;
}
int main(){
scanf("%lld%lld",&n,&m);
for(int i = 1;i <= n;++i)
for(int j = 1;j <= m;++j)
scanf("%lld",&a[i][j]),sum += a[i][j];
for(int i = 1;i <= n;++i)
for(int j = 1;j <= m;++j)
scanf("%lld",&s1[i][j]),sum += s1[i][j];
for(int i = 1;i <= n;++i)
for(int j = 1;j <= m;++j)
scanf("%lld",&sa[i][j]),sum += sa[i][j];
for(int i = 1;i <= n;++i)
for(int j = 1;j <= m;++j)
scanf("%lld",&ss[i][j]),sum += ss[i][j];
s = 0,t = n * m + 1;
for(int i = 1;i <= n;++i)
for(int j = 1;j <= m;++j){
add(s,id(i,j),a[i][j]);
add(id(i,j),s,0);
add(id(i,j),t,s1[i][j]);
add(t,id(i,j),0);
}
pcnt = n * m + 1;
for(int i = 1;i <= n;++i)
for(int j = 1;j <= m;++j){
add(s,++pcnt,sa[i][j]);
add(pcnt,s,0);
add(pcnt,id(i,j),0x3f3f3f3f);
add(id(i,j),pcnt,0);
if(i + 1 <= n){
add(pcnt,id(i + 1,j),0x3f3f3f3f);
add(id(i + 1,j),pcnt,0);
}
if(j + 1 <= m){
add(pcnt,id(i,j + 1),0x3f3f3f3f);
add(id(i,j + 1),pcnt,0);
}
if(i - 1 > 0){
add(pcnt,id(i - 1,j),0x3f3f3f3f);
add(id(i - 1,j),pcnt,0);
}
if(j - 1 > 0){
add(pcnt,id(i,j - 1),0x3f3f3f3f);
add(id(i,j - 1),pcnt,0);
}
}
for(int i = 1;i <= n;++i)
for(int j = 1;j <= m;++j){
add(++pcnt,t,ss[i][j]);
add(t,pcnt,0);
add(id(i,j),pcnt,0x3f3f3f3f);
add(pcnt,id(i,j),0);
if(i + 1 <= n){
add(id(i + 1,j),pcnt,0x3f3f3f3f);
add(pcnt,id(i + 1,j),0);
}
if(j + 1 <= m){
add(id(i,j + 1),pcnt,0x3f3f3f3f);
add(pcnt,id(i,j + 1),0);
}
if(i - 1 > 0){
add(id(i - 1,j),pcnt,0x3f3f3f3f);
add(pcnt,id(i - 1,j),0);
}
if(j - 1 > 0){
add(id(i,j - 1),pcnt,0x3f3f3f3f);
add(pcnt,id(i,j - 1),0);
}
}
std::cout<<sum - dinic();
}