为了准备省选(这话似乎说了很多次了。。。),机房学习了网络流的算法,权衡两个算法,我最终学习了简单好写的ISAP
ISAP的基本思路是利用流量平衡原理,找一条从源点到汇点的增广路径,反向弧加上这条路径上的最小剩余流量,正向弧减去,如果找不到,则调整距离标号。ISAP(大多数都把ISAP当作SAP)网上的资源非常多,这里不再赘述。给出一个非常好的讲ISAP链接:http://www.cnblogs.com/wally/archive/2013/05/03/3054778.html
题目COGS 885 草地排水 (裸题) code:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int map[201][201],n,m;
int pre[201],gap[201],lev[201];
int ISAP(int vs,int vt)
{
memset(pre,-1,sizeof(pre));
memset(gap,0,sizeof(gap));
memset(lev,0,sizeof(lev));
int minl,maxt=0,i,v,u=pre[vs]=vs,aug=2100000000;
maxt=0; gap[0]=vt;
while (lev[vs]<vt)
{
for (v=1;v<=vt;v++)
if (lev[u]==lev[v]+1&&map[u][v]>0)
break;
if (v<=vt)
{
pre[v]=u;
u=v;
if (v==vt)
{
aug=2100000000;
for (i=v;i!=vs;i=pre[i])
if (aug>map[pre[i]][i])
aug=map[pre[i]][i];
maxt+=aug;
for (i=v;i!=vs;i=pre[i])
{
map[pre[i]][i]-=aug;
map[i][pre[i]]+=aug;
}
u=vs;
}
}
else
{
minl=vt;
for (v=1;v<=vt;++v)
if (map[u][v]>0&&minl>lev[v])
minl=lev[v];
gap[lev[u]]--;
if (gap[lev[u]]==0) break;
lev[u]=minl+1;
gap[lev[u]]++;
u=pre[u];
}
}
return maxt;
}
int main()
{
int i,j,u,v,c;
freopen("ditch.in","r",stdin);
freopen("ditch.out","w",stdout);
scanf("%d%d",&n,&m);
for (i=1;i<=n;++i)
{
scanf("%d%d%d",&u,&v,&c);
map[u][v]+=c;
}
printf("%d\n",ISAP(1,m));
fclose(stdin); fclose(stdout);
}
COGS 11 运输问题1 有源有汇有上界无下界最大流 code:
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
int map[101][101],n;
int lev[101],gap[101],pre[101];
int ISAP(int vs,int vt)
{
memset(gap,0,sizeof(gap));
memset(lev,0,sizeof(lev));
memset(pre,-1,sizeof(pre));
int i,minl,u=pre[vs]=vs,v,aug,maxt=0;
gap[0]=vt;
while (lev[vs]<vt)
{
for (v=1;v<=vt;v++)
if (lev[u]==lev[v]+1&&map[u][v]>0)
break;
if (v<=vt)
{
pre[v]=u;
u=v;
if (v==vt)
{
aug=2100000000;
for (i=v;i!=vs;i=pre[i])
if (map[pre[i]][i]<aug)
aug=map[pre[i]][i];
maxt+=aug;
for (i=v;i!=vs;i=pre[i])
{
map[pre[i]][i]-=aug;
map[i][pre[i]]+=aug;
}
u=vs;
}
}
else
{
minl=vt;
for (v=1;v<=vt;++v)
if (map[u][v]>0&&minl>lev[v])
minl=lev[v];
gap[lev[u]]--;
if (gap[lev[u]]==0) break;
lev[u]=minl+1;
gap[lev[u]]++;
u=pre[u];
}
}
return maxt;
}
int main()
{
int i,j;
freopen("maxflowa.in","r",stdin);
freopen("maxflowa.out","w",stdout);
scanf("%d\n",&n);
for (i=1;i<=n;++i)
for (j=1;j<=n;++j)
scanf("%d",&map[i][j]);
printf("%d\n",ISAP(1,n));
fclose(stdin);
fclose(stdout);
}
COGS 12 运输问题2 有源有汇有上界有下界最大流 code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
int map[150][150],n;
int lev[150],pre[150],gap[150];
int ISAP(int vs,int vt)
{
memset(lev,0,sizeof(lev));
memset(gap,0,sizeof(gap));
memset(pre,-1,sizeof(pre));
int i,v,u=pre[vs]=vs,minl,aug,maxt=0;
gap[0]=vt-vs+1;
while (lev[vs]<vt)
{
for (v=vs;v<=vt;v++)
if (map[u][v]>0&&lev[u]==lev[v]+1)
break;
if (v<=vt)
{
pre[v]=u;
u=v;
if (v==vt)
{
aug=2100000000;
for (i=v;i!=vs;i=pre[i])
if (map[pre[i]][i]<aug)
aug=map[pre[i]][i];
maxt+=aug;
for (i=v;i!=vs;i=pre[i])
{
map[pre[i]][i]-=aug;
map[i][pre[i]]+=aug;
}
u=vs;
}
}
else
{
minl=vt;
for (v=vs;v<=vt;++v)
if (map[u][v]>0&&lev[v]<minl)
minl=lev[v];
gap[lev[u]]--;
if (gap[lev[u]]==0) break;
lev[u]=minl+1;
gap[lev[u]]++;
u=pre[u];
}
}
return maxt;
}
int main()
{
int i,j,m,inf,outf,sum,ans,p=0;
int pic[150][150][2];
freopen("maxflowb.in","r",stdin);
freopen("maxflowb.out","w",stdout)
scanf("%d",&n);
for (i=1;i<=n;++i)
for (j=1;j<=2*n;++j)
scanf("%d",&pic[i][(j+1)/2][1-j%2]);
for (i=1;i<=n;++i)
{
inf=0; outf=0;
for (j=1;j<=n;++j)
{
inf+=pic[j][i][0];
outf+=pic[i][j][0];
map[i][j]=pic[i][j][1]-pic[i][j][0];
}
sum=outf-inf;
if (sum<0)
{
map[0][i]=abs(sum);
p+=abs(sum);
}
else
map[i][n+1]=abs(sum);
}
map[n][1]=2100000000;
ans=ISAP(0,n+1);
if (ans==p)
{
map[n][1]=0;
ans=ISAP(1,n);
printf("%d\n",ans);
}
else
printf("0\n");
}
COGS 13 运输问题4 最小费用最大流模板题 code:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int point[101],next[5000],queue[500],dis[101],minn[101],pre[101],e,head,tail,ans=0,n,st,en;
bool exist[101];
struct hp{
int u,v,c,w;
}a[5000];
void add(int u,int v,int c,int w)
{
e++;
a[e].u=u; a[e].v=v; a[e].c=c; a[e].w=w; next[e]=point[u]; point[u]=e;
e++;
a[e].u=v; a[e].v=u; a[e].c=0; a[e].w=0-w; next[e]=point[v]; point[v]=e;
}
bool work(int vs,int vt)
{
memset(exist,false,sizeof(exist));
memset(dis,127,sizeof(dis));
memset(pre,0,sizeof(pre));
int i,j,u,stan=dis[1]; minn[vs]=dis[1];
exist[vs]=true; dis[vs]=0; head=0; tail=1; queue[tail]=vs;
while (head!=tail)
{
head=head%500+1;
u=queue[head];
exist[u]=false;
for (i=point[u];i!=0;i=next[i])
{
if (a[i].c>0&&dis[a[i].v]>dis[u]+a[i].w)
{
dis[a[i].v]=dis[u]+a[i].w;
pre[a[i].v]=i;
minn[a[i].v]=min(minn[u],a[i].c);
if (!exist[a[i].v])
{
tail=tail%500+1;
queue[tail]=a[i].v;
exist[a[i].v]=true;
}
}
}
}
if (dis[vt]==stan) return false;
ans+=dis[vt]*minn[vt];
for (i=pre[vt];i!=0;i=pre[a[i].u])
{
a[i].c-=minn[vt];
a[i^1].c+=minn[vt];
}
return true;
}
int main()
{
int i,j,c,w;
scanf("%d%d%d",&n,&st,&en);
e=1;
for (i=1;i<=n;++i)
for (j=1;j<=n;++j)
{
scanf("%d%d",&c,&w);
if (c) add(i,j,c,w);
}
while (work(st,en));
printf("%d\n",ans);
}
寒假网上授课 Day1 测试题1 肉鸡翅(貌似是出题人自己出的,下面给出题目)
1. 肉鸡翅
要做出绝顶美味让人大快朵颐的肉鸡翅,需要放入很多美味的调料。
此时麦当劳来了一位大客户,他要n*m个鸡翅,并且要求用一个带铁丝网格的巨大n行m列铁板装着鸡翅(每个格子里装一个),每个格子里的鸡翅必须用此客户指定的调料(不尽相同),然后此客户将这个铁板整个带回家当艺术品享用。
铁板被分成n*m 个方格,每一个格子都只能放上带指定调料的鸡翅,调料是不能覆盖的,即假如P格要放x味的调料,那么不能先放上y味的调料再放上x味的调料。
麦当劳首席大厨TYG很崇尚艺术,他买了一台自动洒调料机,有三种洒调料方法:
A. 将同一行某些连续的格子洒上同一种味道的调料,机器损耗的代价为a
B. 将同一列某些连续的格子洒上同一种味道的调料,机器损耗的代价为b
C. 将某一个格子撒上某种味的调料,机器损耗的代价为c
现在TYG想要一种代价最少的方案将这些鸡翅按客户指定的要求撒上调料。
【输入】:meatchickwing.in
本题有多组数据(有很多那种大客户来订单,引进肉鸡品牌后销量明显增了很多),首先第一行输入数据组数,
每组数据的输入如下:
第一行五个整数n,m,a,b,c
接下来一个n*m 的矩阵,表示每一个网格中应洒调料的种类,调料种类用小写字母表示。
(注:不保证字母是连续的出现的。也就是说可能只出现了a和c两种字母)
【输出】:meatchickwing.out
对于每组数据,输出一个整数ans,表示最小代价
样例输入:
2
3 4 11 8 3
aaaa
aaaa
aaaa
4 4 3 5 2
aabc
aabc
bbbb
ccbc
样例输出:
32
23
数据范围:
数据组数不超过 40。
1<=n,m<=30
1<=a,b,c<=100000
终于见到了网络流真正的难点,与差分约束一样,都难在建模上,我们统计一下横的相同的块数toth,纵的相同的块数totl,从s引向toth个点一条上限为a的边,从totl个点引向t一条上限为b的边,对于每个相交的“横块”和“纵块”,在他们之间引一条长度为c的边。跑一遍最大流即可。
理解的姿势是这样的:首先,假设点(i,j),那么他一定同时属于一个“横块”a和”纵块“b,s->a->b->t一定会有一条边是在最小割中,再根据最小割等于最大流的原理,就可以求出最小费用 code:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int map[2000][2000],n,m,a,b,c;
int pre[2000],lev[2000],gap[2000];
struct hp{
int x,y;
}dot[31][31];
int ISAP(int vs,int vt)
{
memset(pre,-1,sizeof(pre));
memset(lev,0,sizeof(lev));
memset(gap,0,sizeof(gap));
int i,v,u=pre[vs]=vs,minl,maxt=0,aug;
gap[0]=vt-vs+1;
while (lev[vs]<vt)
{
for (v=vs;v<=vt;v++)
if (map[u][v]>0&&lev[u]==lev[v]+1)
break;
if (v<=vt)
{
pre[v]=u;
u=v;
if (v==vt)
{
aug=2100000000;
for (i=v;i!=vs;i=pre[i])
if (aug>map[pre[i]][i])
aug=map[pre[i]][i];
maxt+=aug;
for (i=v;i!=vs;i=pre[i])
{
map[pre[i]][i]-=aug;
map[i][pre[i]]+=aug;
}
u=vs;
}
}
else
{
minl=vt;
for (v=vs;v<=vt;++v)
if (minl>lev[v]&&map[u][v]>0)
minl=lev[v];
gap[lev[u]]--;
if (gap[lev[u]]==0) break;
lev[u]=minl+1;
gap[lev[u]]++;
u=pre[u];
}
}
return maxt;
}
int main()
{
int i,j,toth=0,totl=0,t,ti,ans;
char pic[50][50];
freopen("meatchickwing.in","r",stdin);
freopen("meatchickwing.out","w",stdout);
scanf("%d",&t);
for (ti=1;ti<=t;++ti)
{
toth=totl=0;
memset(map,0,sizeof(map));
memset(pic,' ',sizeof(pic));
scanf("%d%d%d%d%d",&n,&m,&a,&b,&c);
for (i=1;i<=n;++i)
for (j=1;j<=m;++j)
cin>>pic[i][j];
for (i=1;i<=n;++i)
for (j=1;j<=m;++j)
{
if (pic[i][j]!=pic[i][j-1])
toth++;
dot[i][j].x=toth;
}
for (j=1;j<=m;++j)
for (i=1;i<=n;++i)
{
if (pic[i][j]!=pic[i-1][j])
totl++;
dot[i][j].y=toth+totl;
}
for (i=1;i<=toth;++i)
map[0][i]=a;
for (i=toth+1;i<=toth+totl;++i)
map[i][toth+totl+1]=b;
for (i=1;i<=n;++i)
for (j=1;j<=m;++j)
map[dot[i][j].x][dot[i][j].y]=c;
ans=ISAP(0,toth+totl+1);
printf("%d\n",ans);
scanf("%*c");
}
fclose(stdin);
fclose(stdout);
}
CODEVS 1436 SCOI 修车
网络流最难的地方就是建模,对于这道题,我们先建立一个超级源点ss,超级汇点tt,然后从ss向每个车引一条流量为1,费用为0的边,在向第i车引向每个修车工一条流量为1,费用为0的边,再从顾客i和技师j的组合向技师j的修车顺序(这辆车是第k个修的)引一条流量为1,费用为k*time[i][j](因为要包含k个人等待的时间);再从技师j的修车顺序向tt引一条流量为1,费用为0的边,跑一遍最小费用最大流即可;理解是这样的,这样建点可以保证最小割一定是n辆车,由于最小割等于最大流,所以跑一遍最小费用最大流再平均一下就是答案;
PS:开循环队列开的稍大一点,程序会快一些,蒟蒻一开始就是因为这事T了好几遍。 code:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
struct hp{
int u,v,c,w;
}a[100000];
int point[2000],next[100000],pre[2000],dis[2000],queue[1000000],minn[2000],head,tail,m,n,ans=0,e;
bool exist[2000];
void add(int i,int j,int c,int w)
{
e++; next[e]=point[i]; point[i]=e;
a[e].u=i; a[e].v=j; a[e].c=c; a[e].w=w;
e++; next[e]=point[j]; point[j]=e;
a[e].u=j; a[e].v=i; a[e].c=0; a[e].w=-w;
}
bool work(int vs,int vt)
{
int u,i,stan;
memset(exist,false,sizeof(exist));
memset(dis,127,sizeof(dis));
memset(minn,0,sizeof(minn));
memset(pre,0,sizeof(pre));
stan=dis[1]; minn[vs]=dis[1]; head=0; tail=1;
dis[vs]=0; exist[vs]=true; queue[tail]=vs;
while (head!=tail)
{
head=head%100000+1;
u=queue[head];
exist[u]=false;
for (i=point[u];i!=0;i=next[i])
{
if (a[i].c>0&&dis[a[i].v]>dis[u]+a[i].w)
{
dis[a[i].v]=dis[u]+a[i].w;
pre[a[i].v]=i;
minn[a[i].v]=min(minn[u],a[i].c);
if (!exist[a[i].v])
{
tail=tail%1000000+1;
queue[tail]=a[i].v;
exist[a[i].v]=true;
}
}
}
}
if (dis[vt]==stan) return false;
else
{
ans+=dis[vt]*minn[vt];
for (i=pre[vt];i!=0;i=pre[a[i].u])
{
a[i].c-=minn[vt];
a[i^1].c+=minn[vt];
}
return true;
}
}
int main()
{
int i,j,k,dian=0;
float avg;
int pic[100][100],c[100][100],kk[100][100];
scanf("%d%d",&m,&n);
for (i=1;i<=n;++i)
for (j=1;j<=m;++j)
scanf("%d",&pic[i][j]);
e=1;
for (i=1;i<=n;++i)
add(0,i,1,0);
dian=n;
for (i=1;i<=n;++i)
for (j=1;j<=m;++j)
{
dian++; c[i][j]=dian;
add(i,dian,1,0);
}
for (j=1;j<=m;++j)
for (k=1;k<=n;++k)
{
dian++; kk[j][k]=dian;
}
for (i=1;i<=n;++i)
for (j=1;j<=m;++j)
for (k=1;k<=n;++k)
add(c[i][j],kk[j][k],1,k*pic[i][j]);
++dian;
for (j=1;j<=m;++j)
for (k=1;k<=n;++k)
add(kk[j][k],dian,1,0);
while (work(0,dian));
avg=ans/(n*1.0);
printf("%0.2f\n",avg);
}
NOI 2012 美食节
题意同上题,但是如果这样做的话,会T掉四个点,于是需要加优化,优化是只有当每个厨师处理掉上一道菜时,才加上下一道菜的边。
PS:循环队列开大了会快,但为毛数组开大了程序会慢呢,蒟蒻数组开大后,还不如裸的做法= = code:
#include<iostream>
#include<cstdio>
#include<cstring>
#define len 1000000
using namespace std;
struct hp{
int u,v,c,w;
}a[10000000];
int queue[1000000],pre[100000],dis[100000],next[10000000],minn[1000000],point[100000],n,m,e,head,tail,ans,dot;
bool exist[100000];
int ai[41],tim[41][101],c[101][101],lev[101],jian[100000],tt;
void add(int u,int v,int c,int w)
{
e++;
next[e]=point[u]; point[u]=e;
a[e].u=u; a[e].v=v; a[e].c=c; a[e].w=w;
e++;
next[e]=point[v]; point[v]=e;
a[e].u=v; a[e].v=u; a[e].c=0; a[e].w=-w;
}
bool work(int vs,int vt)
{
int u,stan,i,cc;
memset(exist,false,sizeof(exist));
memset(dis,127,sizeof(dis));
stan=minn[vs]=dis[1]; head=0; tail=1;
dis[vs]=0; exist[vs]=true; queue[tail]=vs;
while (head!=tail)
{
head=head%len+1;
u=queue[head];
exist[u]=false;
for (i=point[u];i!=0;i=next[i])
{
if (a[i].c>0&&dis[a[i].v]>dis[u]+a[i].w)
{
dis[a[i].v]=dis[u]+a[i].w;
pre[a[i].v]=i;
minn[a[i].v]=min(minn[u],a[i].c);
if (!exist[a[i].v])
{
tail=tail%len+1;
queue[tail]=a[i].v;
exist[a[i].v]=true;
}
}
}
}
if (dis[vt]==stan) return false;
ans+=minn[vt]*dis[vt];
for (i=pre[vt];i!=0;i=pre[a[i].u])
{
a[i].c-=minn[vt];
a[i^1].c+=minn[vt];
}
cc=jian[a[pre[vt]].u];
++lev[cc]; ++dot;
jian[dot]=cc;
for (i=1;i<=n;++i)
add(c[i][cc],dot,1,lev[cc]*tim[i][cc]);
add(dot,tt,1,0);
return true;
}
int main()
{
int i,j,k;
scanf("%d%d",&n,&m);
e=1;
for (i=1;i<=n;++i)
{
scanf("%d",&ai[i]);
add(0,i,ai[i],0);
}
for (i=1;i<=n;++i)
for (j=1;j<=m;++j)
scanf("%d",&tim[i][j]);
dot=n;
for (i=1;i<=n;++i)
for (j=1;j<=m;++j)
{
dot++; c[i][j]=dot;
add(i,c[i][j],ai[i],0);
}
for (i=1;i<=n;++i)
for (j=1;j<=m;++j)
{
jian[dot+j]=j; lev[j]=1;
add(c[i][j],dot+j,1,tim[i][j]);
}
dot+=m;
dot++; tt=dot;
for (j=1;j<=m;++j)
add(dot-1-m+j,dot,1,0);
while (work(0,tt));
printf("%d\n",ans);
}
NOI 2006 最大获利:
noi里第一道网络流的题,其算法是最大权闭合子图,大概是源点向每个用户点连大小为C_i的边,每个中转站点向汇点连大小为P_i的边,每个用户向所对应的两个点连大小为正无穷的边,这样的话最小割一定不会把这三个点分开,然后最大流一边就行了。(PS:貌似需要各种神奇优化。带gap,cur优化的ISAP可过)
code:
#include<iostream>
#include<cstdio>
#include<cstring>
#define inf 2100000000
using namespace std;
struct hp{
int u,v,c;
}a[500000];
int e,n,m;
int p[5001];
int point[56000],next[500000],pre[56000],lev[56000],gap[56000];
int cur[56000];
int ISAP(int vs,int vt)
{
int v,i,u,maxf=0,aug,minl; bool f;
gap[0]=vt-vs+1; u=vs;
while (lev[vs]<vt)
{
f=false;
for (v=cur[u];v!=0;v=next[v])
if (lev[u]==lev[a[v].v]+1&&a[v].c>0)
{f=true; cur[u]=v; break;}
if (f)
{
pre[a[v].v]=v;
u=a[v].v;
if (u==vt)
{
aug=inf;
for (i=v;i!=0;i=pre[a[i].u])
if (aug>a[i].c)
aug=a[i].c;
maxf+=aug;
for (i=v;i!=0;i=pre[a[i].u])
{
a[i].c-=aug;
a[i^1].c+=aug;
}
u=vs;
}
}
else
{
minl=vt;
for (i=point[u];i!=0;i=next[i])
if (minl>lev[a[i].v]&&a[i].c>0)
minl=lev[a[i].v];
gap[lev[u]]--;
if (gap[lev[u]]==0) break;
lev[u]=minl+1;
cur[u]=point[u];
gap[lev[u]]++;
if (u!=vs) u=a[pre[u]].u;
}
}
return maxf;
}
int main()
{
int i,x,y,c,ans,sum=0;
freopen("profit.in","r",stdin);
freopen("profit.out","w",stdout);
scanf("%d%d",&n,&m);
e=1;
for (i=1;i<=n;++i)
{
scanf("%d",&p[i]);
e++;
next[e]=point[i]; cur[i]=point[i]=e; a[e].u=i; a[e].v=n+m+1; a[e].c=p[i];
e++;
next[e]=point[n+m+1]; cur[n+m+1]=next[n+m+1]=e; a[e].u=n+m+1; a[e].v=i; a[e].c=0;
}
for (i=1;i<=m;++i)
{
scanf("%d%d%d",&x,&y,&c); sum+=c;
e++;
next[e]=point[0]; cur[0]=point[0]=e; a[e].u=0; a[e].v=n+i; a[e].c=c;
e++;
next[e]=point[n+i]; cur[n+i]=point[n+i]=e; a[e].u=n+i; a[e].v=0; a[e].c=0;
e++;
next[e]=point[n+i]; cur[n+i]=point[n+i]=e; a[e].u=n+i; a[e].v=x; a[e].c=inf;
e++;
next[e]=point[x]; cur[x]=point[x]=e; a[e].u=x; a[e].v=n+i; a[e].c=0;
e++;
next[e]=point[n+i]; cur[n+i]=point[n+i]=e; a[e].u=n+i; a[e].v=y; a[e].c=inf;
e++;
next[e]=point[y]; cur[y]=point[y]=e; a[e].u=y; a[e].v=n+i; a[e].c=0;
}
ans=ISAP(0,n+m+1);
printf("%d\n",sum-ans);
fclose(stdin); fclose(stdout);
}
SDOI 2010 星际穿越
拆点的思路。对于每一个点,拆为两个点,第二个点表示已经经过(包括跑过去与跳过去 ),向汇点连边。第一个点表示未经过,由源点向其连边。code:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
struct hp{
int u,v,c,w;
}a[60000];
int point[2000],next[60000],n,m,e,ans=0,ai[801];
int queue[100000],dis[2000],pre[2000],minn[2000];
bool exist[2000];
void add(int x,int y,int c,int w)
{
e++; next[e]=point[x]; point[x]=e;
a[e].u=x; a[e].v=y; a[e].c=c; a[e].w=w;
e++; next[e]=point[y]; point[y]=e;
a[e].u=y; a[e].v=x; a[e].c=0; a[e].w=-w;
}
bool work(int vs,int vt)
{
memset(exist,false,sizeof(exist));
memset(dis,127/3,sizeof(dis));
memset(pre,0,sizeof(pre));
int stan=dis[0],u,i,head,tail;
head=0; tail=1; queue[tail]=vs; exist[vs]=true; minn[vs]=dis[0]; dis[vs]=0;
while (head!=tail)
{
head=head%100000+1;
u=queue[head];
exist[u]=false;
for (i=point[u];i!=0;i=next[i])
if (dis[a[i].v]>dis[u]+a[i].w&&a[i].c>0)
{
dis[a[i].v]=dis[u]+a[i].w;
pre[a[i].v]=i;
minn[a[i].v]=min(minn[u],a[i].c);
if (!exist[a[i].v])
{
tail=tail%100000+1;
queue[tail]=a[i].v;
exist[a[i].v]=true;
}
}
}
if (dis[vt]==stan) return false;
ans+=dis[vt]*minn[vt];
for (i=pre[vt];i!=0;i=pre[a[i].u])
{
a[i].c-=minn[vt];
a[i^1].c+=minn[vt];
}
return true;
}
int main()
{
int i,x,y,c;
freopen("starrace.in","r",stdin);
freopen("starrace.out","w",stdout);
scanf("%d%d",&n,&m);
e=1;
for (i=1;i<=n;++i)
{
scanf("%d",&x);
add(0,i*2-1,1,0);
add(0,i*2,1,x);
add(i*2,n*2+1,1,0);
}
for (i=1;i<=m;++i)
{
scanf("%d%d%d",&x,&y,&c);
if (x>y) swap(x,y);
x=x*2-1; y=y*2;
add(x,y,1,c);
}
while (work(0,n*2+1));
printf("%d\n",ans);
fclose(stdin);
fclose(stdout);
}
BZOJ HNOI 2001 软件安装 同 网络流24题 餐巾规划
把餐巾按新旧分成两类,那么很容易考虑到把点拆为两个点,旧毛巾点可以分别留到下一天,也可以花费fa或fb的费用变为新毛巾点,新毛巾点也可以直接有超级源点花费f的费用直接买来,源点向旧毛巾点连边,新毛巾点向汇点连边,跑最小费用最大流即可。code:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
struct hp{
int u,v,c,w;
}a[100001];
int num[10001],ans;
int point[30001],next[100001],n,ai,bi,fa,fb,f,e;
int queue[100001],dis[30001],pre[30001],minn[30001];
bool exist[30001];
void add(int x,int y,int c,int w)
{
e++; next[e]=point[x]; point[x]=e;
a[e].u=x; a[e].v=y; a[e].c=c; a[e].w=w;
e++; next[e]=point[y]; point[y]=e;
a[e].u=y; a[e].v=x; a[e].c=0; a[e].w=-w;
}
bool work(int vs,int vt)
{
int head,tail,u,i,stan;
memset(exist,false,sizeof(exist));
memset(dis,127/3,sizeof(dis));
memset(pre,0,sizeof(pre));
head=0; tail=1; stan=minn[vs]=dis[0];
dis[vs]=0; exist[vs]=true; queue[tail]=vs;
while (head!=tail)
{
head=head%100000+1;
u=queue[head]; exist[u]=false;
for (i=point[u];i!=0;i=next[i])
if (a[i].c>0&&dis[a[i].v]>dis[u]+a[i].w)
{
dis[a[i].v]=dis[u]+a[i].w;
pre[a[i].v]=i;
minn[a[i].v]=min(minn[u],a[i].c);
if (!exist[a[i].v])
{
tail=tail%100000+1;
queue[tail]=a[i].v;
exist[a[i].v]=true;
}
}
}
if (stan==dis[vt]) return false;
ans+=minn[vt]*dis[vt];
for (i=pre[vt];i!=0;i=pre[a[i].u])
{
a[i].c-=minn[vt];
a[i^1].c+=minn[vt];
}
return true;
}
int main()
{
int i,j;
scanf("%d%d%d%d%d%d",&n,&ai,&bi,&f,&fa,&fb);
e=1;
for (i=1;i<=n;++i)
scanf("%d",&num[i]);
for (i=1;i<=n;++i)
{
add(0,i*2-1,num[i],f);
add(0,i*2,num[i],0);
add(i*2-1,n*2+1,num[i],0);
}
for (i=1;i<=n-1;++i)
add(i*2,(i+1)*2,2100000000,0);
for (i=1;i<=n;++i)
{
if (i+ai+1<=n)
add(i*2,(i+ai+1)*2-1,2100000000,fa);
if (i+bi+1<=n)
add(i*2,(i+bi+1)*2-1,2100000000,fb);
}
while (work(0,n*2+1));
printf("%d\n",ans);
}
网络流24题 骑士共存 二分图最大匹配 code:
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
int a[700001];
bool visit[40001]={false};
int point[40001]={0},next[400001],match[40001]={0},e,n,m,dot[201][201]={0};
int dx[8]={-2,-2,-1,-1,1,1,2,2},dy[8]={-1,1,-2,2,-2,2,-1,1},t=0;
bool f[250][250]={0};
void add(int x,int y)
{
a[++e]=y;
next[e]=point[x]; point[x]=e;
}
bool find(int u)
{
int i;
++t;
for (i=point[u];i!=0;i=next[i])
{
if (!visit[a[i]])
{
visit[a[i]]=true;
if (!match[a[i]]||find(match[a[i]]))
{
match[a[i]]=u;
return true;
}
}
}
return false;
}
int main()
{
int i,x,y,j,k,ans=0,xx,yy,dotm=0,temp=0;
freopen("knight.in","r",stdin);
freopen("knight.out","w",stdout);
scanf("%d%d",&n,&m);
for (i=1;i<=m;++i)
{
scanf("%d%d",&x,&y);
f[x][y]=true;
}
for (i=1;i<=n;++i)
for (j=1;j<=n;++j)
{dot[i][j]=++dotm;}
e=0;
for (i=1;i<=n;++i)
for (j=1;j<=n;++j)
if ((i+j)%2==0&&!f[i][j])
for (k=0;k<8;++k)
{
xx=i+dx[k];
yy=j+dy[k];
if (f[xx][yy]) continue;
if (xx<1||xx>n) continue;
if (yy<1||yy>n) continue;
add(dot[i][j],dot[xx][yy]);
}
for (i=1;i<=n;++i)
for (j=1;j<=n;++j)
if ((i+j)%2==0&&!f[i][j])
{
temp++;
memset(visit,false,sizeof(visit));
if (find(dot[i][j]))
ans++;
}
ans=n*n-m-ans;
printf("%d\n",ans);
fclose(stdin);
fclose(stdout);
}
网络流24题 数字梯形 最大不相交路径,注意答案2中路径也可以在终点相交 code:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
struct hp{
int u,v,c;
}a[10000];
int next[10000],point[500];
int f[101][101];
bool d[201];
int cur[500],pre[500],lev[500],gap[500];
int m,n,e;
void add(int x,int y,int c)
{
e++; next[e]=point[x]; cur[x]=point[x]=e;
a[e].u=x; a[e].v=y; a[e].c=c;
e++; next[e]=point[y]; cur[y]=point[y]=e;
a[e].u=y; a[e].v=x; a[e].c=0;
}
int ISAP(int vs,int vt)
{
int u,v,i,maxf=0,aug,minl; bool f;
memset(pre,0,sizeof(pre));
memset(gap,0,sizeof(gap));
memset(lev,0,sizeof(lev));
gap[0]=vt-vs+1; u=vs;
while (lev[vs]<vt)
{
f=false;
for (v=cur[u];v!=0;v=next[v])
if (a[v].c>0&&lev[u]==lev[a[v].v]+1)
{cur[u]=v; f=true; break;}
if (f)
{
pre[a[v].v]=v;
u=a[v].v;
if (u==vt)
{
aug=2100000000;
for (i=v;i!=0;i=pre[a[i].u])
if (aug>a[i].c)
aug=a[i].c;
maxf+=aug;
for (i=v;i!=0;i=pre[a[i].u])
{
a[i].c-=aug;
a[i^1].c+=aug;
}
u=vs;
}
}
else
{
minl=vt;
for (i=point[u];i!=0;i=next[i])
if (a[i].c>0&&minl>lev[a[i].v])
minl=lev[a[i].v];
gap[lev[u]]--;
if (gap[lev[u]]==0) break;
lev[u]=minl+1;
cur[u]=point[u];
gap[lev[u]]++;
if (u!=vs) u=a[pre[u]].u;
}
}
return maxf;
}
void find(int x)
{
int i;
d[x]=true;
for (i=point[x];i!=0;i=next[i])
if (a[i].c&&!d[a[i].v]) find(a[i].v);
}
int main()
{
int i,j,num,ans,x,sum=0; char c;
freopen("shuttle.in","r",stdin);
freopen("shuttle.out","w",stdout);
scanf("%d%d",&m,&n);
e=1;
for (i=1;i<=m;++i)
{
scanf("%d",&x);
add(0,i,x);
sum+=x;
scanf("%*c%c",&c);
num=0;
while (c!='\n'&&c!='\r')
{
if (c==' ')
{
f[i][0]++; f[i][f[i][0]]=m+num;
add(i,m+num,2100000000);
num=0;
}
else
num=num*10+(int)(c)-48;
scanf("%c",&c);
}
f[i][0]++; f[i][f[i][0]]=m+num;
add(i,m+num,2100000000);
}
for (i=1;i<=n;++i)
{
scanf("%d",&x);
add(m+i,m+n+1,x);
}
ans=ISAP(0,n+m+1);
find(0);
for (i=1;i<=m;++i)
if (d[i]) printf("%d ",i);
printf("\n");
for (i=m+1;i<=n+m;++i)
if (d[i]) printf("%d ",i-m);
printf("\n");
ans=sum-ans;
printf("%d\n",ans);
fclose(stdin);
fclose(stdout);
}
太空飞行计划 最大权闭合子图,如果流量不为0则此方案在答案中出现,注意后悔边的存在 code:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
struct hp{
int u,v,c;
}a[10000];
int next[10000],point[500];
int f[101][101];
bool d[201];
int cur[500],pre[500],lev[500],gap[500];
int m,n,e;
void add(int x,int y,int c)
{
e++; next[e]=point[x]; cur[x]=point[x]=e;
a[e].u=x; a[e].v=y; a[e].c=c;
e++; next[e]=point[y]; cur[y]=point[y]=e;
a[e].u=y; a[e].v=x; a[e].c=0;
}
int ISAP(int vs,int vt)
{
int u,v,i,maxf=0,aug,minl; bool f;
memset(pre,0,sizeof(pre));
memset(gap,0,sizeof(gap));
memset(lev,0,sizeof(lev));
gap[0]=vt-vs+1; u=vs;
while (lev[vs]<vt)
{
f=false;
for (v=cur[u];v!=0;v=next[v])
if (a[v].c>0&&lev[u]==lev[a[v].v]+1)
{cur[u]=v; f=true; break;}
if (f)
{
pre[a[v].v]=v;
u=a[v].v;
if (u==vt)
{
aug=2100000000;
for (i=v;i!=0;i=pre[a[i].u])
if (aug>a[i].c)
aug=a[i].c;
maxf+=aug;
for (i=v;i!=0;i=pre[a[i].u])
{
a[i].c-=aug;
a[i^1].c+=aug;
}
u=vs;
}
}
else
{
minl=vt;
for (i=point[u];i!=0;i=next[i])
if (a[i].c>0&&minl>lev[a[i].v])
minl=lev[a[i].v];
gap[lev[u]]--;
if (gap[lev[u]]==0) break;
lev[u]=minl+1;
cur[u]=point[u];
gap[lev[u]]++;
if (u!=vs) u=a[pre[u]].u;
}
}
return maxf;
}
void find(int x)
{
int i;
d[x]=true;
for (i=point[x];i!=0;i=next[i])
if (a[i].c&&!d[a[i].v]) find(a[i].v);
}
int main()
{
int i,j,num,ans,x,sum=0; char c;
freopen("shuttle.in","r",stdin);
freopen("shuttle.out","w",stdout);
scanf("%d%d",&m,&n);
e=1;
for (i=1;i<=m;++i)
{
scanf("%d",&x);
add(0,i,x);
sum+=x;
scanf("%*c%c",&c);
num=0;
while (c!='\n'&&c!='\r')
{
if (c==' ')
{
f[i][0]++; f[i][f[i][0]]=m+num;
add(i,m+num,2100000000);
num=0;
}
else
num=num*10+(int)(c)-48;
scanf("%c",&c);
}
f[i][0]++; f[i][f[i][0]]=m+num;
add(i,m+num,2100000000);
}
for (i=1;i<=n;++i)
{
scanf("%d",&x);
add(m+i,m+n+1,x);
}
ans=ISAP(0,n+m+1);
find(0);
for (i=1;i<=m;++i)
if (d[i]) printf("%d ",i);
printf("\n");
for (i=m+1;i<=n+m;++i)
if (d[i]) printf("%d ",i-m);
printf("\n");
ans=sum-ans;
printf("%d\n",ans);
fclose(stdin);
fclose(stdout);
}
网络流24题 圆桌聚餐 建图很好想,但输出路径。。。 code:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
struct hp{
int u,v,c;
}a[100000];
int point[500],next[100000],e,m,n;
int ansi[151][151],num[151];
int cur[500],pre[500],lev[500],gap[500];
void add(int x,int y,int c)
{
e++; next[e]=point[x]; cur[x]=point[x]=e;
a[e].u=x; a[e].v=y; a[e].c=c;
e++; next[e]=point[y]; cur[y]=point[y]=e;
a[e].u=y; a[e].v=x; a[e].c=0;
}
int ISAP(int vs,int vt)
{
int minl,maxf=0,aug,v,i,u; bool f,d;
memset(pre,0,sizeof(pre));
memset(gap,0,sizeof(gap));
memset(lev,0,sizeof(lev));
gap[0]=vt-vs+1; u=vs;
while (lev[vs]<vt)
{
f=false;
for (v=cur[u];v!=0;v=next[v])
if (a[v].c>0&&lev[u]==lev[a[v].v]+1)
{f=true; cur[u]=v; break;}
if (f)
{
pre[a[v].v]=v;
u=a[v].v;
if (u==vt)
{
aug=2100000000;
for (i=v;i!=0;i=pre[a[i].u])
if (aug>a[i].c)
aug=a[i].c;
maxf+=aug;
for (i=v;i!=0;i=pre[a[i].u])
{
a[i].c-=aug;
a[i^1].c+=aug;
}
u=vs;
}
}
else
{
minl=vt;
for (i=point[u];i!=0;i=next[i])
if (a[i].c>0&&minl>lev[a[i].v])
minl=lev[a[i].v];
gap[lev[u]]--;
if (!gap[lev[u]]) break;
lev[u]=minl+1;
gap[lev[u]]++;
cur[u]=point[u];
if (u!=vs) u=a[pre[u]].u;
}
}
return maxf;
}
int main()
{
int ans,i,j,sum=0,x;
freopen("roundtable.in","r",stdin);
freopen("roundtable.out","w",stdout);
scanf("%d%d",&m,&n);
e=1;
for (i=1;i<=m;++i)
{
scanf("%d",&x); num[i]=x;
add(0,i,x);
sum+=x;
}
for (i=1;i<=n;++i)
{
scanf("%d",&x);
add(m+i,n+m+1,x);
}
for (i=1;i<=m;++i)
for (j=1;j<=n;++j)
add(i,j+m,1);
ans=ISAP(0,n+m+1);
if (ans==sum)
{
printf("1\n");
for (i=1;i<=m;++i)
{
for (j=point[i];j!=0;j=next[j])
if (a[j].c==0)
printf("%d ",a[j].v-m);
printf("\n");
}
}
else
printf("0\n");
fclose(stdin);
fclose(stdout);
}
网络流24题 魔术球问题 贪心可解,还是蛮显然的,还发现一个公式 (n+1)^2/2+1,谁能告诉我为什么 code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
int tp[61];
int main()
{
int i=0,n,j;
bool f;
freopen("balla.in","r",stdin);
freopen("balla.out","w",stdout);
scanf("%d",&n);
while (true)
{
i++;
f=false;
for (j=1;j<=n;++j)
if (sqrt(tp[j]+i)==floor(sqrt(tp[j]+i))||tp[j]==0)
{
tp[j]=i;
f=true;
break;
}
if (!f) break;
}
printf("%d\n",i-1);
fclose(stdin);
fclose(stdout);
}
网络流24题 最长递增子序列 我只会费用流做法,但标解是最大流,速度稍慢,但还能接受(PS:我一开始居然想到了拆点 = =)code:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
struct hp{
int u,v,c,w;
}a[300000];
int n,num[501],e,maxf=0,maxn=0;
int point[501],next[300000];
int queue[100001];
int exist[501],dis[501],pre[501],minn[501];
void add(int x,int y,int c,int w)
{
e++; next[e]=point[x]; point[x]=e;
a[e].u=x ;a[e].v=y; a[e].c=c; a[e].w=w;
e++; next[e]=point[y]; point[y]=e;
a[e].u=y; a[e].v=x; a[e].c=0; a[e].w=-w;
}
bool work(int vs,int vt)
{
int u,i,stan,head,tail;
memset(exist,false,sizeof(exist));
memset(dis,127/3,sizeof(dis));
memset(pre,0,sizeof(pre));
stan=minn[vs]=dis[0]; head=0; tail=1;
dis[vs]=0; exist[vs]=true; queue[tail]=vs;
while (head!=tail)
{
head=head%100000+1;
u=queue[head]; exist[u]=false;
for (i=point[u];i!=0;i=next[i])
if (a[i].c>0&&dis[a[i].v]>dis[u]+a[i].w)
{
dis[a[i].v]=dis[u]+a[i].w;
pre[a[i].v]=i;
minn[a[i].v]=min(minn[u],a[i].c);
if (!exist[a[i].v])
{
tail=tail%100000+1;
queue[tail]=a[i].v;
exist[a[i].v]=true;
}
}
}
if (stan==dis[vt]||-dis[vt]<maxn) return false;
maxf+=minn[vt];
for (i=pre[vt];i!=0;i=pre[a[i].u])
{
a[i].c-=minn[vt];
a[i^1].c+=minn[vt];
}
return true;
}
int main()
{
int i,j;
int f[501];
freopen("alis.in","r",stdin);
freopen("alis.out","w",stdout);
scanf("%d",&n);
for (i=1;i<=n;++i)
scanf("%d",&num[i]);
f[1]=1;
for (i=2;i<=n;++i)
{
f[i]=1;
for (j=1;j<=i-1;++j)
if (num[j]<=num[i]&&f[j]+1>f[i])
f[i]=f[j]+1;
maxn=max(maxn,f[i]);
}
printf("%d\n",maxn);
e=1;
for (i=1;i<=n;++i)
{
add(0,i,1,0);
add(i,n+1,1,-1);
for (j=i+1;j<=n;++j)
if (num[j]>=num[i])
add(i,j,1,-1);
}
maxf=0;
while (work(0,n+1)); printf("%d\n",maxf);
memset(point,0,sizeof(point));
memset(next,0,sizeof(next));
e=1;
for (i=1;i<=n;++i)
{
if (i==1)
add(0,i,2100000000,0);
else add(0,i,1,0);
if (i==n)
add(i,n+1,2100000000,-1);
else add(i,n+1,1,-1);
for (j=i+1;j<=n;++j)
if (num[j]>=num[i])
add(i,j,1,-1);
}
maxf=0;
while (work(0,n+1)); printf("%d\n",maxf);
fclose(stdin);
fclose(stdout);
}
网络流24题 负载平衡 建图很巧妙,貌似用到了流量平衡的原理 code:
#include<iostream>
#include<cstdio>
#include<cstring>
#define inf 2100000000
using namespace std;
struct hp{
int u,v,c,w;
}a[100001];
int n,ans,e,point[151],next[100001],num[101];
bool exist[151];
int queue[10001],pre[151],minn[151],dis[151];
void add(int x,int y,int c,int w)
{
e++; next[e]=point[x]; point[x]=e;
a[e].u=x; a[e].v=y; a[e].c=c; a[e].w=w;
e++; next[e]=point[y]; point[y]=e;
a[e].u=y; a[e].v=x; a[e].c=0; a[e].w=-w;
}
bool work(int vs,int vt)
{
int u,stan,i,head,tail;
memset(exist,false,sizeof(exist));
memset(pre,0,sizeof(pre));
memset(dis,127/3,sizeof(dis));
minn[vs]=stan=dis[0]; head=0; tail=1;
queue[tail]=vs; dis[vs]=0; exist[vs]=true;
while (head!=tail)
{
head=head%10000+1;
u=queue[head]; exist[u]=false;
for (i=point[u];i!=0;i=next[i])
if (a[i].c>0&&dis[a[i].v]>dis[u]+a[i].w)
{
dis[a[i].v]=dis[u]+a[i].w;
pre[a[i].v]=i;
minn[a[i].v]=min(minn[u],a[i].c);
if (!exist[a[i].v])
{
exist[a[i].v]=true;
tail=tail%10000+1;
queue[tail]=a[i].v;
}
}
}
if (stan==dis[vt]) return false;
ans+=minn[vt]*dis[vt];
for (i=pre[vt];i!=0;i=pre[a[i].u])
{
a[i].c-=minn[vt];
a[i^1].c+=minn[vt];
}
return true;
}
int main()
{
int i,sum=0,aver;
freopen("overload.in","r",stdin);
freopen("overload.out","w",stdout);
scanf("%d",&n);
for (i=1;i<=n;++i)
{
scanf("%d",&num[i]);
sum+=num[i];
}
aver=sum/n;
e=1;
for (i=1;i<=n;++i)
{
if (i+1<=n)
{add(i,i+1,inf,1); add(i+1,i,inf,1);}
else
{add(n,1,inf,1); add(1,n,inf,1);}
if (num[i]<aver)
add(i,n+1,aver-num[i],0);
if (num[i]>aver)
add(0,i,num[i]-aver,0);
}
while (work(0,n+1));
printf("%d\n",ans);
fclose(stdin);
fclose(stdout);
}