暑期培训结束之际,开始做网络流。
根据网上网络流的题目,断断续续做到了今天。
很少有能自己想到怎么构图的,有的虽然构图对了,但还是wa了。
还经常犯同样的错误。
为了改变这种状况,特写此文。
========================================
一个值得一提的是,在没有经过认真的思考之前尽量不要去搜题解或是看discuss。
在平时训练的时候,这些习惯没有太多好处。
从别人那里得到的总是不如自己想出来的映像深刻和令人兴奋。
看同校大神师兄的oj账号,都是有不少提交但没ac的题目,反观自己,一想不出就搜题解,看discuss,拿别人的ac程序暴力对拍。。。
这样虽然题过了,但是下次再碰到同类型的题是不是就能做出来了?这个也只有自己知道了。又或者比赛时写出了八九成却wa了,然后找不到bug,只能赛后再去”取经“?
仅供自勉。
========================================
求最大流用的是ISAP,模板来自NotOnlySuccess的博客(神犇一枚,代码相当飘逸,不过最近博客经常打不开,貌似空间到期了Orz)
首先是模板题:
hdu3549 Flow Problem
注意一下重边。
反思:
memset第二个参数是要赋的值,记成是区间长度。。。
我就是传说中抄模板都能抄错的神一样的男子。。。
浙大模板:
#include <cstring>
#include <cstdio>
#include <iostream>
using namespace std;
#define DEBUG(x) cout<<#x<<":"<<x<<endl;
const int Max=20;
int mat[Max][Max],flow[Max][Max];
int pre[Max],d[Max];
int n,m;
void input()
{
memset(mat,0,sizeof(mat));
int u,v,c;
while(n--)
{
scanf("%d%d%d",&u,&v,&c);
mat[u][v]+=c;
}
}
const int INF=0x7fffffff;
int sta[Max],p,q;
int maxflow()
{
int f=0,s,t,cur;
s=1;
t=m;
memset(flow,0,sizeof(flow));
while(1)
{
memset(d,0,sizeof(d));
memset(pre,0,sizeof(pre));
d[s]=INF;
pre[s]=s;
cur=s;
for(p=q=0; p<=q&&!pre[t]; cur=sta[p++])
for(int i=1,j; i<=m; i++)
if(!pre[i]&&(j=mat[cur][i]-flow[cur][i]))
pre[sta[q++]=i]=cur,d[i]=min(d[cur],j);
else if(!pre[i]&&(j=flow[i][cur]))
pre[sta[q++]=i]=-cur,d[i]=min(d[cur],j);
if(!pre[t])break;
for(int i=t; i!=s;)
if(pre[i]>0)flow[pre[i]][i]+=d[t],i=pre[i];
else flow[i][-pre[i]]-=d[t],i=-pre[i];//- -
}
for(int i=1; i<=m; i++)f+=flow[s][i];
return f;
}
int main()
{
int t,cas=0;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&m,&n);
input();
printf("Case %d: %d\n",++cas,maxflow());
}
return 0;
}
NotOnlySuccess:
#include <cstdio>
#include <cstring>
#define CC(a,x) memset(a,x,sizeof(a))
#define FOR(i,j,k) for(int i=j;i<=k;i++)
#define FF(i,n) for(int i=1;i<=n;i++)
inline void checkmin(int &aug,int &maze)
{
if(aug==-1||maze<aug)
aug=maze;
}
#define M 20
int maze[M][M];
int gap[M],dis[M],pre[M],cur[M];
int sap(int s,int t,int nodenum)
{
CC(cur,0);
CC(dis,0);
CC(gap,0);
int u = pre[s] = s,maxflow = 0,aug = -1;
gap[0] = nodenum;
while(dis[s] < nodenum)
{
loop:
FOR(v,cur[u],nodenum) if(maze[u][v] && dis[u] == dis[v] + 1)
{
checkmin(aug,maze[u][v]);
pre[v] = u;
u = cur[u] = v;
if(v == t)
{
maxflow += aug;
for(u = pre[u]; v != s; v = u,u = pre[u])
{
maze[u][v] -= aug;
maze[v][u] += aug;
}
aug = -1;
}
goto loop;
}
int mindis= nodenum-1;
FF(v,nodenum) if(maze[u][v] && mindis> dis[v])
{
cur[u] = v;
mindis= dis[v];
}
if((--gap[dis[u]])== 0) break;
gap[dis[u] = mindis+1] ++;
u = pre[u];
}
return maxflow;
}
int main()
{
int t,cas=0;
int n,m,u,v,w;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
CC(maze,0);
while(m--)
{
scanf("%d%d%d",&u,&v,&w);
maze[u][v]+=w;
}
printf("Case %d: %d\n",++cas,sap(1,n,n));
}
return 0;
}
poj1273/hdu1532 Drainage Ditches
#include <cstdio>
#include <cstring>
using namespace std;
const int Max=222;
#define FOR(i,j,k) for(int i = j;i < k;i++)
#define _FOR(i,j,k) for(int i = j;i <= k;i++)
#define RE(i,n) for(int i = 0;i < n;i++)
#define _RE(i,n) for(int i = 1;i <= n;i++)
int maze[Max][Max];
void input(int n)
{
memset(maze,0,sizeof(maze));
int s,e,c;
while(n--)
{
scanf("%d%d%d",&s,&e,&c);
maze[s][e] += c;
}
}
int pre[Max],d[Max];
int isap(int s,int t,int n)
{
int MaxFlow=0,u,aug=-1;
memset(d,0,sizeof(d));
pre[u = s] = s;
while(d[s] < n)
{
LOOP:
_RE(v,n)if(maze[u][v]&&d[u]==d[v]+1)
{
if(aug==-1||maze[u][v]<aug)aug = maze[u][v];
pre[v] = u;
u = v;
if(u==t)
{
MaxFlow += aug;
for(u = pre[u]; v!=s; v = u,u = pre[u])
{
maze[u][v] -= aug;
maze[v][u] += aug;
}
aug = -1;
}
goto LOOP;
}
int MinDis = n-1;
_RE(v,n)if(maze[u][v]&&MinDis>d[v])
{
MinDis = d[v];
}
d[u] = MinDis+1;
u=pre[u];
}
return MaxFlow;
}
int main()
{
int n,m;
while(~scanf("%d%d",&n,&m))
{
input(n);
printf("%d\n",isap(1,m,m));
}
}
接下来的需要简单的构图:
poj1274 The Perfect Stall
题意:
有N头奶牛和M个stalls ,每头奶牛都只有在特定的某个或某几个stalls 时才产奶,求最多能有多少头奶牛产奶。
做法:
奶牛与所有它喜欢的stalls都连一条容量为1 的边。
从源点到每头奶牛连一条容量为1的边。
从每个stalls到汇点连一条容量为1的边。
#include <cstdio>
#include <cstring>
using namespace std;
const int Max=405;
#define FOR(i,j,k) for(int i = j;i < k;i++)
#define _FOR(i,j,k) for(int i = j;i <= k;i++)
#define RE(i,n) for(int i = 0;i < n;i++)
#define _RE(i,n) for(int i = 1;i <= n;i++)
#define SET(a,x) memset(a,x,sizeof(a))
int maze[Max][Max];
void input(int n,int m)
{
SET(maze,0);
int e,t;
_RE(i,n)
{
scanf("%d",&t);
while(t--)
{
scanf("%d",&e);
e += n;
maze[i][e] = 1;
}
maze[0][i]=1;
}
_FOR(i,n+1,n+m)
maze[i][n+m+1]=1;
}
int pre[Max],d[Max],cur[Max],gap[Max];
int isap(int s,int t,int n)
{
int MaxFlow = 0,u,aug = -1;
SET(d,0);
gap[0] = n;
pre[u = s] = s;
while(d[s] < n)
{
LOOP:
_FOR(v,cur[u],n)if(maze[u][v]&&d[u]==d[v]+1)
{
if(aug==-1||maze[u][v]<aug)aug = maze[u][v];
pre[v] = u;
u = cur[u] = v;
if(u==t)
{
MaxFlow += aug;
for(u = pre[u]; v!=s; v = u,u = pre[u])
{
maze[u][v] -= aug;
maze[v][u] += aug;
}
aug = -1;
}
goto LOOP;
}
int MinDis = n-1;
_RE(v,n)if(maze[u][v]&&MinDis>d[v])
{
cur[u]=v;
MinDis = d[v];
}
if(--gap[d[u]]==0)break;
gap[d[u] = MinDis+1]++;
u=pre[u];
}
return MaxFlow;
}
int main()
{
int n,m;
while(~scanf("%d%d",&n,&m))
{
input(n,m);
printf("%d\n",isap(0,m+n+1,n+m+2));
}
}
还写了一份二分匹配的:
#include <cstdio>
#include <cstring>
const int Max=220;
int maze[Max][Max];
int n,m;
void input()
{
memset(maze,0,sizeof(maze));
for(int u=1,v,t;u<=n;u++)
{
scanf("%d",&t);
while(t--)
{
scanf("%d",&v);
maze[u][v]=1;
}
}
}
int linkx[Max],vis[Max];
bool hungry(int u)
{
for(int v=1;v<=m;v++)
if(maze[u][v])
{
if(vis[v])
continue;
vis[v]=1;
if(linkx[v]==-1||hungry(linkx[v]))
{
linkx[v]=u;
return true;
}
}
return false;
}
int MaxMatch()
{
int ret=0;
memset(linkx,-1,sizeof(linkx));
for(int i=1;i<=n;i++)
{
memset(vis,0,sizeof(vis));
if(hungry(i))
ret++;
}
return ret;
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
input();
printf("%d\n",MaxMatch());
}
}
poj1698 Alice's Chance
题意:
一部电影需要需要D天才能拍完,且只能在一周中的某几天拍摄,还必须在W周之内拍完。
有N部这样的电影,问Alice能否N部电影都拍。
做法:
每部电影与其对应的星期几连一条容量为1的边。如一部电影只能在周一、周三拍,且需要在3周内拍完,则该电影须与前3周的每个星期一和星期三都连一条边。
从源点到每部电影连一条容量为D的边。
从每周的每一天到汇点连一条容量为1的边。
反思:
YES & Yes ←←
一开始构图的时候,只区分周几,而不区分第几周,无限wa。。
提交时编译器选C++wa了,完全一样的代码G++过了,以后还是交G++了。。(C++是VC,有被微软做过优化,与G++存在区别。)
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define DEBUG(x) cout<<#x<<":"<<x<<endl
#define SET(a,x) memset(a,x,sizeof(a))
#define RE(i,n) for(int i = 0;i < n;i ++)
#define _RE(i,n) for(int i = 1;i <= n;i ++)
#define _FOR(i,j,k) for(int i = j;i <= k;i ++)
const int Max=400,aWeek=7;
int maze[Max][Max];
int week[aWeek],sink;
int input(int n)
{
SET(maze,0);
int w,ret = 0,MAX=0;
_RE(i,n)
{
_RE(j,aWeek)
scanf("%d",&maze[i][n+j]);
scanf("%d",&maze[0][i]);
ret += maze[0][i];
scanf("%d",&w);
if(MAX<w) MAX = w;
_RE(j,aWeek)if(maze[i][n + j])
{
RE(k,w)
maze[i][n+j+aWeek*k] = 1;
}
}
sink = n + MAX * aWeek + 1;
_FOR(i,n + 1,sink - 1)maze[i][sink] = 1;
return ret;
}
int dis[Max],pre[Max],cur[Max],gap[Max];
int isap(int s,int t,int n)
{
int maxflow = 0,aug = -1,u;
SET(dis,0);
SET(cur,0);
SET(gap,0);
pre[u = s] = s;
gap[0]=n;
while(dis[s]<n)
{
loop:
_FOR(v,cur[u],n)if(maze[u][v]&&dis[u]==dis[v]+1)
{
if(aug==-1||maze[u][v]<aug)aug = maze[u][v];
pre[v] = u;
u = cur[u] = v;
if(u==t)
{
maxflow += aug;
for(u = pre[u]; v!=s; v = u,u = pre[u])
{
maze[u][v] -= aug;
maze[v][u] += aug;
}
aug = -1;
}
goto loop;
}
int mindis=n-1;
_FOR(v,0,n)if(maze[u][v]&&mindis>dis[v])
{
cur[u] = v;
mindis = dis[v];
}
if(--gap[dis[u]]==0)
{
break;
}
gap[dis[u] = mindis+1]++;
u = pre[u];
}
return maxflow;
}
int main()
{
// freopen("in.txt","r",stdin);
int t,n;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
if(input(n)==isap(0,sink,sink+1))
puts("Yes");
else
puts("No");
}
}
poj2112 Optimal Milking
题意:
有K台机器和C头奶牛,他们都在一个特定的位置上。
给出他们之间的直接相连的道路的距离(如果没有则为0)。
要求一台机器最多只能为M头奶牛工作。
在满足所有奶牛都能有机器的前提下,所有奶牛到达机器都有一个距离(并不一定是直达的距离),取其中最大的一个,求这个距离最小可以是多少。
做法:
(首先用floyd求最短路)
每次二分最大距离,假设当前得到mid,就只考虑floyd后距离不大于mid的边。
对于不大于mid的边:都连一条容量为1的边。
从源点到每台机器连一条容量为M的边。
从每头奶牛到汇点连一条容量为1的边。
反思:
对floyd理解不深。一开始floyd的三层循环写错了,把最外层的放在了最里层,无限wa。。
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define DEBUG(x) cout<<#x<<":"<<x<<endl
#define SET(a,x) memset(a,x,sizeof(a))
#define RE(i,n) for(int i = 0;i < n;i ++)
#define _RE(i,n) for(int i = 1;i <= n;i ++)
#define _FOR(i,j,k) for(int i = j;i <= k;i ++)
const int Max=300,INF=1e9;
int k,c,m,n;
int s,t;
int maze[Max][Max],cap[Max][Max];
void input() {
SET(cap,0);
_RE(i,n)
_RE(j,n) {
scanf("%d",&maze[i][j]);
}
}
int floyd() {
int MAX=0;
_RE(k,n)
_RE(i,n)
_RE(j,n)
{
if((maze[i][k]&&maze[k][j])&&(!maze[i][j]||maze[i][k]+maze[k][j]<maze[i][j]))
maze[i][j]=maze[i][k]+maze[k][j];
if(maze[i][j]>MAX)
MAX=maze[i][j];
}
return MAX;
}
void SetCap(int dis) {
SET(cap,0);
_FOR(i,1,k)
_FOR(j,k+1,n) //!第二层循环是指奶牛,应从K+1开始
if(maze[i][j]&&maze[i][j]<=dis)
cap[i][j]=1;
_RE(i,k)
cap[s][i]=m;
_FOR(i,k+1,n)
cap[i][t]=1;
}
int dis[Max],pre[Max],cur[Max],gap[Max];
int isap(int s,int t,int n) {
int maxflow = 0,aug = -1,u;
SET(dis,0);
SET(cur,0);
SET(gap,0);
pre[u = s] = s;
gap[0]=n;
while(dis[s]<n) {
loop:
_FOR(v,cur[u],n)if(cap[u][v]&&dis[u]==dis[v]+1) {
if(aug==-1||cap[u][v]<aug)aug = cap[u][v];
pre[v] = u;
u = cur[u] = v;
if(u==t) {
maxflow += aug;
for(u = pre[u]; v!=s; v = u,u = pre[u]) {
cap[u][v] -= aug;
cap[v][u] += aug;
}
aug = -1;
}
goto loop;
}
int mindis=n-1;
_FOR(v,0,n)if(cap[u][v]&&mindis>dis[v]) {
cur[u] = v;
mindis = dis[v];
}
if(--gap[dis[u]]==0) {
break;
}
gap[dis[u] = mindis+1]++;
u = pre[u];
}
return maxflow;
}
int main() {
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
while(~scanf("%d%d%d",&k,&c,&m)) {
n=k+c;
input();
floyd();
s=0;
t=k+c+1;
int l=0,mid,r=n*200,ret=r;
while(l<=r) {
mid=l+r;
mid>>=1;
SetCap(mid);
if(isap(s,t,t+1)==c)
r=mid-1,ret=mid;
else
l=mid+1;
}
printf("%d\n",ret);
}
}
poj2455 Secret Milking Machine
题意:
N个地点被P条路所连接,要从地点1到达地点N共T次。
要让该过程走过的最长的路尽可能的小。
做法:
每次二分最大距离,设当前为mid,就只考虑不大于mid的边。
对于不大于mid的边都连一条容量为1的边。
从1到N求最大流,看是否不小于T。
反思:
邻接表的上限至少要是边上限的2倍,re了N次。。
建边的时候其反向边是0,不是1。
不该用引用的地方用了引用,对模板理解不深。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define SET(a,x) memset(a,x,sizeof(a))
#define RE(i,N) for(int i = 0; i < N; i++)
#define _RE(i,N) for(int i = 1; i <= N; i ++)
const int MAXV = 220,MAXE = 40040 << 1;
int NV,P,T;
struct edgeSave {
int a,b,l;
} save[MAXE];
int head[MAXV],ecnt;
struct edge {
int v,w,next;
} e[MAXE];
bool cmp(edgeSave a,edgeSave b) {
return a.l < b.l;
}
inline void insert(int u,int v,int w) {
e[ecnt].v=v;
e[ecnt].w=w;
e[ecnt].next=head[u];
head[u]=ecnt++;
}
void build(int m) {
ecnt=0;
SET(head,-1);
RE(i,P) if(save[i].l <= m) {
insert(save[i].a,save[i].b,1);
insert(save[i].b,save[i].a,1);
} else {
break;
}
}
int cur[MAXV] ,dis[MAXV] ,pre[MAXV] ,gap[MAXV];
int ISap(int s ,int t ,int NV) {
int maxflow = 0, aug = -1, u;
_RE(i,NV)cur[i] = head[i];
SET(dis,0);
SET(gap,0);
gap[0] = NV;
u = pre[s] = s;
while (dis[s] < NV) {
loop:
for(int &i = cur[u]; i != -1; i = e[i].next) {
int v = e[i].v;
if(e[i].w && dis[u] == dis[v] + 1) {
if(aug == -1 || e[i].w < aug) aug = e[i].w;
pre[v] = u;
u = v;
if(u == t) {
maxflow += aug;
for(u = pre[u]; v != s; v = u, u = pre[u]) {
e[cur[u]].w -= aug;
e[cur[u]^1].w +=aug;
}
aug = -1;
}
goto loop;
}
}
int mindis = NV - 1;
// !此处不能用引用类型,为此wa了数次,对模板理解不深
for(int i = head[u]; i != -1; i = e[i].next) {
int v = e[i].v;
if(e[i].w && dis[v] < mindis) {
cur[u] = i;
mindis = dis[v];
}
}
if(--gap[dis[u]] == 0)break;
gap[ dis[u] = mindis + 1 ] ++;
u = pre[u];
}
return maxflow;
}
int main() {
while(~scanf("%d%d%d",&NV,&P,&T)) {
RE(i ,P) {
scanf("%d%d%d",&save[i].a,&save[i].b,&save[i].l);
}
sort(save, save + P, cmp);
int l,m,r;
l = 0;
r = 1e6 + 6;
while(l <= r) {
m = (l + r) >> 1;
build(m);
int tmp = ISap(1,NV,NV);
if(tmp >= T)
r = m - 1;
else
l = m + 1;
}
cout<<l<<endl;
}
}
poj3189 Steady Cow Assignment
题意:
有N头奶牛和B个barns。
每个barns都有一个容量。
给出每头奶牛在选择barns时的志愿顺序。
所有奶牛都能分配到barns,设第i头奶牛分配到的barns为其第x[i]志愿,
求max{x[i],1<=i<=B,1<=x[i]<=B}与min{x[i]}之差最小可以是多少。答案是该差加1。
做法:
首先枚举差为0的,即看能否找到一种分配,使得所有的x[i]都等于x。
能的话答案就为1,不能的话就枚举长度为2的。即看能否找到一种分配使得max{x[i]}-min{x[i]}=2
。。。。。。
如果当前枚举差为L的,则可能的分配为每头奶牛 从第1志愿到第L+1志愿都考虑,
从第2志愿到的L+2志愿
。。。
从B-L+1到第B志愿
如果都不能找到满足的分配,则L++,继续找。
反思:
一开始理解错题意,以为是让max{x[i]}最小,看懂题意很重要。
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define RE(i,n) for(int i = 0; i < n; i++)
#define _RE(i,n) for(int i = 1; i <= n; i++)
const int MAXN = 1100, MAXB = 30;
int choice[MAXN][MAXB],cap[MAXB];
int N,B;
struct edge {
int v,w,next;
} e[MAXN *MAXB *2];
int ecnt,head[MAXN];
void insert(int u,int v,int w) {
e[ecnt].v = v;
e[ecnt].w = w;
e[ecnt].next = head[u];
head[u] = ecnt++;
e[ecnt].v = u;
e[ecnt].w = 0;
e[ecnt].next = head[v];
head[v] = ecnt++;
}
void build(int begin,int len) {
ecnt = 0;
memset(head,-1,sizeof(head));
int s = 0;
_RE(i,N)insert(s,i,1);
int end = begin + len;
_RE(i,N)
for(int j = begin; j <= end; j++)
insert(i,choice[i][j],1);
int t = N + B + 1;
_RE(j,B)
insert(j + N,t,cap[j]);
}
int dis[MAXN], pre[MAXN], gap[MAXN], cur[MAXN];
int isap(int s,int t,int NV) {
int maxflow = 0, aug = -1, u;
memset(dis,0,sizeof(dis));
memset(gap,0,sizeof(gap));
for(int i = 0; i <= NV; i++) cur[i] = head[i];
u = pre[s] = s;
gap[0] = NV;
while(dis[s] < NV) {
loop:
for(int &i = cur[u]; i != -1; i = e[i].next) {
int v = e[i].v;
if(e[i].w && dis[u] == dis[v] + 1) {
if(aug == -1 || e[i].w <aug) aug = e[i].w;
pre[v] = u;
u = v;
if(u == t) {
maxflow += aug;
for(u = pre[u]; v != s; v = u, u = pre[u]) {
e[cur[u]].w -= aug;
e[cur[u]^1].w += aug;
}
aug = -1;
}
goto loop;
}
}
int mindis = NV;
for(int i = head[u]; i != -1; i = e[i].next) {
int v = e[i].v;
if(e[i].w && dis[v] < mindis) {
mindis = dis[v];
cur[u] = i;
}
}
if((--gap[dis[u]]) == 0) break;
dis[u] = mindis + 1;
gap[dis[u]]++;
u = pre[u];
}
return maxflow;
}
int main() {
// freopen("in.txt","r",stdin);
while(~scanf("%d%d",&N,&B)) {
_RE(i,N)
_RE(j,B) {
scanf("%d",&choice[i][j]);
choice[i][j] += N;
}
_RE(i,B)
scanf("%d",cap + i);
int ret = 0, s = 0, t = N + B + 1;
bool flag = 1;
for(int i = 0; i <B && flag; i++)//higest - lowest : i
for(int j = 1, end = B - i; j <= end && flag; j++){
ret = i + 1;
build(j,i);
if(isap(s,t,t+1) >= N)
flag = 0;
}
printf("%d\n",ret);
}
return 0;
}
poj1637 Sightseeing tour
题意:
混合图求是否存在欧拉回路。
做法:
参考google到的做法写的。下面是自己的理解。
由于无向边只需走一遍,在确定其走向后可当做有向边。因此我们可以先任意给定它一个方向,使之当做有向边。然后就成了一幅有向图。
对于有向图,存在欧拉回路的充要条件是每个点的出度等于入度。但由于有一些无向边被我们任意确定了一个方向,所以必要条件(不是充要。如只考虑有向边的时候,A顶点的入度为10,出度为2;A点还有6条无向边与之相连,无论这6条无向边被我们当成入边还是出边,最后都能使入度出度之差为偶数;但即使这6条边都当做出边,也不能使A点出入度相等。)变成每个点的入度和出度之差为偶数。
在把所有无向边任意定向,并经过上面的预判之后,就可以用网络流进一步确认是否存在欧拉回路。
求最大流时,所有有向边都不连,无向边就按之前任意确定的方向当做有向边连接。
设x[i]为每个点入度与出度之差。若x[i]>0,则从该点到汇点连一条容量为x[i]/2的边。
若x[i]<0,则从源点到该点连一条容量为-x[i]/2的边。
反思:
边的上限又开小了。。ORz
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int maxm = 220, maxs = 1010 * 2 + maxm * 2;
int head[maxm],in[maxm],out[maxm];
struct edge {
int v,w,next;
} e[maxs];
int ecnt;
void insert(int u,int v,int w,int ww = 0) {
e[ecnt].v = v;
e[ecnt].w = w;
e[ecnt].next = head[u];
head[u] = ecnt++;
e[ecnt].v = u;
e[ecnt].w = ww;
e[ecnt].next = head[v];
head[v] = ecnt++;
}
int cur[maxm],pre[maxm],gap[maxm],dis[maxm];
int isap(int s,int t,int n) {
int maxflow = 0,aug = -1,u;
memset(gap,0,sizeof(gap));
memset(dis,0,sizeof(dis));
gap[0] = n;
for(int i = 0; i < n; i++)
cur[i] = head[i];
pre[u = s] = s;
while(dis[s] < n) {
loop:
for(int &i = cur[u]; i != -1; i = e[i].next) {
int v = e[i].v;
if(e[i].w && dis[u] == dis[v] + 1) {
if(aug == -1 || e[i].w < aug)
aug = e[i].w;
pre[v] = u;
u = v;
if(u == t) {
maxflow += aug;
for(u = pre[u]; v != s; v = u, u = pre[u]) {
e[cur[u]].w -= aug;
e[cur[u] ^ 1].w +=aug;
}
aug = -1;
}
goto loop;
}
}
int mindis = n;
for(int i = head[u]; i != -1; i = e[i].next) {
int v = e[i].v;
if(e[i].w && dis[v] < mindis) {
mindis = dis[v];
cur[u] = i;
}
}
if(--gap[dis[u]] == 0) break;
gap[dis[u] = mindis + 1]++;
u = pre[u];
}
return maxflow;
}
int main() {
// freopen("data.txt","r",stdin);
int n;
int m,s;
scanf("%d",&n);
while(n--) {
scanf("%d%d",&m,&s);
memset(head,-1,sizeof(head));
memset(in,0,sizeof(in));
memset(out,0,sizeof(out));
ecnt = 0;
int x,y,d;
while(s--) {
scanf("%d%d%d",&x,&y,&d);
out[x]++;in[y]++;
if(d == 0) {
insert(x,y,1);
}
}
bool flag = true;
int sum = 0,s = 0, t = m + 1;
for(int i = 1; i <= m; i++) {
x = in[i] - out[i];
y = x > 0 ? x : -x;
if(y & 1) {
flag = false;
break;
}
if(x > 0) {
insert(i,t,x / 2);
sum += x/2;
} else if(x < 0)
insert(s,i,-x / 2);
}
if(!flag || ( isap(s,t,m + 2)) != sum)
puts("impossible");
else
puts("possible");
}
}
poj3498 March of the Penguins
题意:
给出N块浮冰的坐标、上面的企鹅数量、在消失前最多能有多少只企鹅离开(每次有企鹅离开,浮冰都会更接近消失)。
问所有的企鹅可以在哪些浮冰上会面。
做法:
对于每块浮冰,既有企鹅数量,又有消失次数。只从源点往这些点连一条边的话,只能表示其中一个。这样只控制了该点的入边,出边由于有很多,看似不能控制。
但是,我们可以把每个点分成两个点,如点1映射成点2、点3,从源点到点2表示企鹅数量,从点2到点3表示消失次数即可。也就是说把点2和点3看成一个整体,点2只有入边,点3只有出边。然后再把所有距离不大于D的边都连一条容量为sum(企鹅总数量)的边。
反思:
一开始把sum写成了N,wa了一次,所幸是自己找出来的。。ORz
#include <cstdio>
#include <iostream>
#include <cstring>
#include <cmath>
using namespace std;
#define DEBUG(x) cout<<#x<<":"<<x<<endl;
#define SET(a,x) memset(a,x,sizeof(a))
#define RE(i,n) for(int i = 0; i < (n); i++)
#define _FOR(i,j,k) for(int i = (j); i <= (k); i++)
const int MAXN = 110,MAXV = MAXN << 1;
struct floes {
int x,y,n,m;
} save[MAXN];
int mat[MAXN][MAXN];
int N,S,T,sum;
double D;
inline double dist(int i,int j) {
return ((save[i].x - save[j].x) * (save[i].x - save[j].x) + (save[i].y - save[j].y) * (save[i].y - save[j].y) );
}
void calculate() {
SET(mat,0);
double DD = D * D;
RE(i,N)
RE(j,N)
if(i != j && dist(i,j) <= DD)
mat[i][j] = 1;
}
int map[MAXV][MAXV];
void setMap(int tar) {
T = tar << 1;
SET(map,0);
RE(i,N)
map[i << 1][i << 1 | 1] = save[i].m;
map[T][T | 1] = 0;
RE(i,N)
RE(j,N)if(mat[i][j]) {
map[i << 1 | 1][j << 1] =sum;//!!
}
S = N << 1;
RE(i,N)
map[S][i << 1] = save[i].n;
map[S][T] = 0;
}
int cur[MAXV],dis[MAXV],pre[MAXV],gap[MAXV];
int isap(int s,int t,int n) {
int maxflow = 0,u,aug = -1;
SET(cur,0);
SET(dis,0);
SET(gap,0);
pre[u = s] = s;
gap[0] = n;
while(dis[s] < n) {
loop:
_FOR(v,cur[u],n)if(map[u][v] && dis[u] == dis[v] + 1) {
if(aug == -1 || map[u][v] < aug)
aug = map[u][v];
pre[v] = u;
u = cur[u] = v;
if(u == t) {
maxflow += aug;
for(u = pre[u]; v != s; v = u, u =pre[u]) {
map[u][v] -= aug;
map[v][u] += aug;
}
aug = -1;
}
goto loop;
}
int mindis = n;
_FOR(v,0,n)if(map[u][v] && dis[v] < mindis) {
mindis = dis[v];
cur[u] = v;
}
if(--gap[dis[u]] == 0)break;
gap[dis[u] =mindis + 1]++;
u = pre[u];
}
return maxflow;
}
int main() {
// freopen("in.txt","r",stdin);
int t;
for(scanf("%d",&t); t--;) {
sum = 0;
scanf("%d%lf",&N,&D);
RE(i,N) {
scanf("%d%d%d%d",&save[i].x,&save[i].y,&save[i].n,&save[i].m);
sum += save[i].n;
}
calculate();
bool first = true;
RE(i,N) {
setMap(i);
if(sum -save[i].n == isap(S,T,S | 1)) {
if(first)printf("%d",i),first = false;
else printf(" %d",i);
}
}
if(first)
puts("-1");
else
puts("");
}
return 0;
}
最小割:
hdu3987 Harry Potter and the Forbidden Forest
题意:
混合图,破坏一些边后使点0和点n-1不相通,要求破坏的边权之和最小,求这些边的条数。
做法:
首先,根据最小割最大流定理,求最大流后那些满流的边就是要破坏掉的。
1.把每条边的权W变成W*E+1(E为大于边数的正整数),然后求一次最大流maxflow。答案即为maxflow%E。
这是因为不满流的边 所通过的流量必然为E的倍数。只有满流的边 所通过的流量%E=1。
2.求一遍最大流。把所有满流的边权值赋为1,非满流的赋为INF。再求一遍最大流即为答案。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
using namespace std;
#define RE(i,n) for(int i = 0; i < (n); i++)
typedef long long ll;
const int N = 1000 + 10;
const int M = (100000 << 2) + 10;
struct edge {
int v;
ll w;
int next;
} e[M];
int head[N],ecnt;
int n,m;
void init() {
memset(head,-1,sizeof(head));
ecnt = 0;
}
void addedge(int u,int v,ll w) {
edge tmp = {v,w,head[u]};
e[ecnt] = tmp;
head[u] = ecnt++;
edge tmp2 = {u,0,head[v]};
e[ecnt] = tmp2;
head[v] = ecnt++;
}
void input() {
scanf("%d%d",&n,&m);
int u,v;
ll w,d;
RE(i,m) {
scanf("%d%d%I64d%I64d",&u,&v,&w,&d);
w = w * M + 1;
addedge(u,v,w);
if(d)
addedge(v,u,w);
}
}
int dis[N],pre[N],gap[N],cur[N];
ll isap(int s,int t,int n) {
ll maxflow = 0;
RE(i,n)
cur[i] = head[i];
memset(dis,0,sizeof(dis));
memset(gap,0,sizeof(gap));
gap[0] = n;
int u;
ll aug = -1;
pre[s] = u = s;
while(dis[s] < n) {
loop:
for(int& i = cur[u]; i != -1; i = e[i].next) {
int v = e[i].v;
if(e[i].w && dis[u] == dis[v] + 1) {
if(aug == -1 || e[i].w < aug)
aug = e[i].w;
pre[v] = u;
u = v;
if(u == t) {
maxflow += aug;
for(u = pre[u]; v != s; v = u, u = pre[u]) {
e[cur[u]].w -= aug;
e[cur[u] ^ 1].w += aug;
}
aug = -1;
}
goto loop;
}
}
int mindis = n;
for(int i = head[u]; i != -1; i = e[i].next) {
int v = e[i].v;
if(e[i].w && dis[v] < mindis) {
mindis = dis[v];
cur[u] = i;
}
}
if(-- gap[dis[u]] == 0)
break;
gap[dis[u] = mindis + 1] ++;
u = pre[u];
}
return maxflow;
}
int main() {
int t,cas = 0;
scanf("%d",&t);
while(t--) {
init();
input();
printf("Case %d: %I64d\n",++cas,isap(0,n-1,n) % M );
}
return 0;
}
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
using namespace std;
#define RE(i,n) for(int i = 0; i < (n); i++)
typedef long long ll;
const int N = 1000 + 10;
const int M = (100000 << 2) + 10;
struct edge {
int v;
ll w,c;
int next;
} e[M];
int head[N],ecnt;
int n,m;
void init() {
memset(head,-1,sizeof(head));
ecnt = 0;
}
void addedge(int u,int v,ll w) {
edge tmp = {v,w,w,head[u]};
e[ecnt] = tmp;
head[u] = ecnt++;
edge tmp2 = {u,0,0,head[v]};
e[ecnt] = tmp2;
head[v] = ecnt++;
}
void input() {
scanf("%d%d",&n,&m);
int u,v;
ll w,d;
RE(i,m) {
scanf("%d%d%I64d%I64d",&u,&v,&w,&d);
// w = w * M + 1;
addedge(u,v,w);
if(d)
addedge(v,u,w);
}
}
int dis[N],pre[N],gap[N],cur[N];
ll isap(int s,int t,int n) {
ll maxflow = 0;
RE(i,n)
cur[i] = head[i];
memset(dis,0,sizeof(dis));
memset(gap,0,sizeof(gap));
gap[0] = n;
int u;
ll aug = -1;
pre[s] = u = s;
while(dis[s] < n) {
loop:
for(int& i = cur[u]; i != -1; i = e[i].next) {
int v = e[i].v;
if(e[i].w && dis[u] == dis[v] + 1) {
if(aug == -1 || e[i].w < aug)
aug = e[i].w;
pre[v] = u;
u = v;
if(u == t) {
maxflow += aug;
for(u = pre[u]; v != s; v = u, u = pre[u]) {
e[cur[u]].w -= aug;
e[cur[u] ^ 1].w += aug;
}
aug = -1;
}
goto loop;
}
}
int mindis = n;
for(int i = head[u]; i != -1; i = e[i].next) {
int v = e[i].v;
if(e[i].w && dis[v] < mindis) {
mindis = dis[v];
cur[u] = i;
}
}
if(-- gap[dis[u]] == 0)
break;
gap[dis[u] = mindis + 1] ++;
u = pre[u];
}
return maxflow;
}
const int INF = 8e8;
void rebuild() {
RE(i,ecnt) {
if(i & 1)
e[i].w = 0;
else {
if(e[i].w == 0)
e[i].w = 1;
else
e[i].w = INF;
}
}
}
int main() {
int t,cas = 0;
scanf("%d",&t);
while(t--) {
init();
input();
isap(0,n-1,n);
rebuild();
printf("Case %d: %I64d\n",++cas,isap(0,n-1,n) % M );
}
return 0;
}