为了准备省选(这话似乎说了很多次了。。。),机房学习了网络流的算法,权衡两个算法,我最终学习了简单好写的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); }