今天学习了一下ISAP算法,简单的进行了网络流的实现。
cogs885 草地排水
题目大意:赤裸裸的网络流模板题。
#include<iostream> #include<cstdio> using namespace std; int map[201][201]={0},dis[201]={0},gap[201]={0},pre[201]={0}; int sap(int stt,int enn) { int i,y,ans=0,u; bool f=false; gap[0]=enn;u=stt;pre[stt]=stt; while(dis[stt]<enn) { f=false; for (i=1;i<=enn;++i) { if (map[u][i]>0&&dis[u]==dis[i]+1) { f=true; break; } } if (f) { pre[i]=u;u=i; if (u==enn) { y=2100000000; for (i=enn;i!=stt;i=pre[i]) y=min(y,map[pre[i]][i]); ans+=y; for (i=enn;i!=stt;i=pre[i]) { map[pre[i]][i]-=y; map[i][pre[i]]+=y; } u=stt; } } else { --gap[dis[u]];y=enn; if (gap[dis[u]]==0) return ans; for (i=1;i<=enn;++i) if (map[u][i]>0&&dis[i]<y) y=dis[i]; dis[u]=y+1; ++gap[dis[u]]; u=pre[u]; } } return ans; } int main() { freopen("ditch.in","r",stdin); freopen("ditch.out","w",stdout); int n,m,i,j,si,ei,ci; scanf("%d%d",&m,&n); for (i=1;i<=m;++i) { scanf("%d%d%d",&si,&ei,&ci); map[si][ei]+=ci; } printf("%d\n",sap(1,n)); fclose(stdin); fclose(stdout); }
cogs11运输问题1
题目大意:赤裸裸的网络流模板题。
思路:这一版是用的next数组,能处理较多边的情况,节省了空间和时间,不过写的时候总是忘记判断边权是否大于0,所以。。。
#include<iostream> #include<cstdio> using namespace std; struct use{ int st,en,va; }edge[20001]; int point[101]={0},next[20001]={0},pre[101]={0},dis[101]={0},gap[101]={0}; int sap(int stt,int enn) { int i,j,x,y,u,ans=0; bool f=false; gap[0]=enn;u=stt; while(dis[stt]<enn) { f=false;y=point[u]; while (y!=0) { if (dis[u]==dis[edge[y].en]+1&&edge[y].va>0) { f=true;break; } y=next[y]; } if (f) { pre[edge[y].en]=y;u=edge[y].en;y=2100000000; if (u==enn) { for (i=enn;i!=stt;i=edge[pre[i]].st) y=min(y,edge[pre[i]].va); ans+=y; for (i=enn;i!=stt;i=edge[pre[i]].st) { x=(pre[i]-1)^1+1; edge[pre[i]].va-=y; edge[x].va+=y; } u=stt; } } else { --gap[dis[u]]; if (gap[dis[u]]==0) return ans; y=enn; for (i=point[u];i!=0;i=next[i]) if (edge[i].va>0) y=min(y,dis[edge[i].en]); dis[u]=y+1; ++gap[dis[u]]; if (u!=stt) u=edge[pre[u]].st; } } return ans; } int main() { freopen("maxflowa.in","r",stdin); freopen("maxflowa.out","w",stdout); int n,i,j,tot=0,x; scanf("%d",&n); for (i=1;i<=n;++i) for (j=1;j<=n;++j) { scanf("%d",&x); if (x!=0) { ++tot;next[tot]=point[i];point[i]=tot; edge[tot].st=i;edge[tot].en=j;edge[tot].va=x; ++tot;next[tot]=point[j];point[j]=tot; edge[tot].st=j;edge[tot].en=i;edge[tot].va=0; } } printf("%d\n",sap(1,n)); fclose(stdin); fclose(stdout); }
寒假测试 T1
题目大意:n*m的网格内放调料,一个格子上不能放不同颜色的调料(可以重复放一种),有三种放调料的方法:1)放一行上连续几个格子,代价为a;2)放一列上连续几个格子,代价为b;3)单独放一个格子,代价为c。求出放满调料的最小代价和。
思路:在测试的时候直接就跳过了这道题,组里有些搜索的全部超时。这道题的正解是网络流,因为对网络流的理解不够深刻,所以没能很快反应过来。
应该对于读进来的nm矩阵进行预处理,将能横向放的所有格子都找出来(一定是同种颜色在一行上最长的连续子段),将他们与超级源点连边,容量为a;再找纵向的,与超级汇点连边,代价为b;最后将横条和纵条有交点的连边,代价为c。这样我们求出这个网络流的最小割,就是能满足题目要求的最小代价了。其实这样建图不难理解,我们其实就是将一个点放到三种情况里,只要是最小割就一定能填满所有格子,同时保证代价最小。
在实现的时候,用next数组实现的sap,开小了数组(这里最多可能有6n^2的边,包括正反边,而不是3n^2),改了之后才很快的a了。
现在发现网络流中的建模的过程十分复杂,情况很多,以后一定要多练习,多思考,熟练掌握。
#include<cstdio> #include<iostream> #include<cstring> using namespace std; struct use{ int st,en,va; }edge[10000]; int map[40][40]={0},tot,n,m,gap[3000]={0},dis[3000]={0},pre[3000]={0},mapp[40][40][2]={0}, point[3000]={0},next[10000]={0}; void add(int stt,int enn,int vaa) { ++tot;next[tot]=point[stt];point[stt]=tot; edge[tot].st=stt;edge[tot].en=enn;edge[tot].va=vaa; ++tot;next[tot]=point[enn];point[enn]=tot; edge[tot].st=enn;edge[tot].en=stt;edge[tot].va=0; } int sap(int st,int en) { int i,j,u,v,minn,ans=0; bool f; memset(gap,0,sizeof(gap)); memset(dis,0,sizeof(dis)); memset(pre,0,sizeof(pre)); gap[0]=en-st+1;u=st; while (dis[st]<en) { f=false; for (i=point[u];i!=0;i=next[i]) { if (dis[edge[i].en]+1==dis[u]&&edge[i].va>0) { f=true;break; } } if (f) { pre[edge[i].en]=i;u=edge[i].en; if (u==en) { minn=2100000000; for (i=en;i!=st;i=edge[pre[i]].st) minn=min(minn,edge[pre[i]].va); ans+=minn; for (i=en;i!=st;i=edge[pre[i]].st) { edge[pre[i]].va-=minn; edge[pre[i]^1].va+=minn; } u=st; } } else { --gap[dis[u]]; if (gap[dis[u]]==0) return ans; minn=en+1; for (i=point[u];i!=0;i=next[i]) if (edge[i].va>0) minn=min(minn,dis[edge[i].en]); dis[u]=minn+1; ++gap[dis[u]]; if (u!=st) u=edge[pre[u]].st; } } return ans; } int main() { freopen("meatchickwing.in","r",stdin); freopen("meatchickwing.out","w",stdout); int t,i,j,a,b,c,x,y,totd,tot1; char ch; scanf("%d",&t); while(t) { memset(map,0,sizeof(map)); memset(edge,0,sizeof(edge)); memset(mapp,0,sizeof(mapp)); memset(edge,0,sizeof(edge)); memset(point,0,sizeof(point)); memset(next,0,sizeof(next)); totd=0;tot=1; scanf("%d%d%d%d%d",&n,&m,&a,&b,&c); for (i=1;i<=n;++i) { for (j=1;j<=m;++j) { cin>>ch; map[i][j]=ch-'a'+1; } } for (i=1;i<=n;++i) for (j=1;j<=m;++j) { if (map[i][j]!=map[i][j-1]) ++totd; mapp[i][j][0]=totd; } tot1=totd; for (j=1;j<=m;++j) for (i=1;i<=n;++i) { if (map[i][j]!=map[i-1][j]) ++totd; mapp[i][j][1]=totd; } for (i=1;i<=tot1;++i) add(0,i,a); for (i=tot1+1;i<=totd;++i) add(i,totd+1,b); for (i=1;i<=n;++i) for (j=1;j<=m;++j) add(mapp[i][j][0],mapp[i][j][1],c); printf("%d\n",sap(0,totd+1)); --t; } fclose(stdin); fclose(stdout); }
cogs14搭配飞行员
题目大意:无源无汇网络流模板题。
思路:对于无源无汇问题,我们只需要加入超级源点和超级汇点,并向对应的点连边,容量为1(一名驾驶员只能安排一次)就可以了。
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; struct use{ int st,en,va; }edge[10000]; int next[10000]={0},point[102]={0},dis[102]={0},pre[102]={0},gap[102]={0},tot; void add(int i,int j,int v) { ++tot;next[tot]=point[i];point[i]=tot; edge[tot].st=i;edge[tot].en=j;edge[tot].va=v; ++tot;next[tot]=point[j];point[j]=tot; edge[tot].st=j;edge[tot].en=i;edge[tot].va=v; } int sap(int stt,int enn) { int i,j,u,v,minn,ans=0; bool f=false; gap[0]=enn-stt+1;u=stt; while(dis[stt]<enn) { f=false; for (i=point[u];i!=0;i=next[i]) if (dis[u]==dis[edge[i].en]+1&&edge[i].va>0) { f=true;break; } if (f) { pre[edge[i].en]=i;u=edge[i].en; if (u==enn) { minn=2100000000; for (i=enn;i!=stt;i=edge[pre[i]].st) minn=min(minn,edge[pre[i]].va); ans+=minn; for (i=enn;i!=stt;i=edge[pre[i]].st) { edge[pre[i]].va-=minn; edge[pre[i]^1].va+=minn; } u=stt; } } else { --gap[dis[u]]; if (gap[dis[u]]==0) return ans; minn=enn+1; for (i=point[u];i!=0;i=next[i]) if (edge[i].va) minn=min(minn,dis[edge[i].en]); dis[u]=minn+1; ++gap[dis[u]]; } } return ans; } int main() { int n,m,i,j,a,b; freopen("flyer.in","r",stdin); freopen("flyer.out","w",stdout); tot=1; scanf("%d%d",&n,&m); while(scanf("%d%d",&a,&b)==2) { if (a>b) swap(a,b); add(a,b,1); } for (i=1;i<=m;++i) add(0,i,1); for (i=m+1;i<=n;++i) add(i,n+1,1); printf("%d\n",sap(0,n+1)); fclose(stdin); fclose(stdout); }
cogs12运输问题2
题目大意:有上下界的最大流
思路:建图的时候进行更改,从一条边的起点向终点连一条(x-y)(x为上限,y为下限)的边,从超级源点向终点连一条y的边,从起点向超级汇点连一条x的边,从原图中的汇点向源点连一条正无穷的边(为了使原图为无源无汇的)。做一遍sap,如果从超级源点流出的弧都满载,就是有可行解,这个时候汇点到源点的流量(也就是源点到汇点的残余流量->即edge.va的值)就是流过的下限的流量。删掉超级源汇点,以及汇点向源点连的那条正无穷的边,对源汇点做一遍最大流,与之前下限流量之和就是答案。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; struct use{ int st,en,va; }edge[100001]; int point[202]={0},next[100001]={0},pre[202]={0},dis[202]={0},gap[202]={0},tot=0; int sap(int stt,int enn) { int i,j,x,y,u,ans=0; bool f=false; memset(gap,0,sizeof(gap)); memset(dis,0,sizeof(dis)); memset(pre,0,sizeof(pre)); gap[0]=enn-stt+1;u=stt; while(dis[stt]<enn) { f=false;y=point[u]; while (y!=0) { if (dis[u]==dis[edge[y].en]+1&&edge[y].va>0) { f=true;break; } y=next[y]; } if (f) { pre[edge[y].en]=y;u=edge[y].en; if (u==enn) { y=2100000000; for (i=enn;i!=stt;i=edge[pre[i]].st) y=min(y,edge[pre[i]].va); ans+=y; for (i=enn;i!=stt;i=edge[pre[i]].st) { edge[pre[i]].va-=y; edge[pre[i]^1].va+=y; } u=stt; } } else { --gap[dis[u]]; if (gap[dis[u]]==0) return ans; y=enn+1; for (i=point[u];i!=0;i=next[i]) if (edge[i].va>0) y=min(y,dis[edge[i].en]); dis[u]=y+1; ++gap[dis[u]]; if (u!=stt) u=edge[pre[u]].st; } } return ans; } void add(int i,int j,int v) { ++tot;next[tot]=point[i];point[i]=tot; edge[tot].st=i;edge[tot].en=j;edge[tot].va=v; ++tot;next[tot]=point[j];point[j]=tot; edge[tot].st=j;edge[tot].en=i;edge[tot].va=0; } int main() { freopen("maxflowb.in","r",stdin); freopen("maxflowb.out","w",stdout); int n,i,j,x,y,ans,t=0; bool f=false; scanf("%d",&n); tot=1; for (i=1;i<=n;++i) for (j=1;j<=n;++j) { scanf("%d%d",&y,&x); if (x!=0) { if (y>0) { add(i,j,x-y);add(0,j,y);add(i,n+1,y);t+=y; } else add(i,j,x); } } add(n,1,1000000000); ans=sap(0,n+1); if (ans==t) { ans=edge[tot].va; edge[tot].va=edge[tot-1].va=0; for (i=point[0];i!=0;i=next[i]) edge[i].va=edge[i^1].va=0; for (i=point[n+1];i!=0;i=next[i]) edge[i].va=edge[i^1].va=0; printf("%d\n",sap(1,n)+ans); } else printf("0\n"); fclose(stdin); fclose(stdout); }
cogs14运输问题4
题目大意:最小费用最大流
思路:用spfa做费用的最短路,更新可行流,然后增广就可以了,直到找不到可行流。
#include<iostream> #include<cstdio> #include<cstring> #define len 10000000 using namespace std; struct use{ int st,en,va,co; }edge[20000]; int que[10000001]={0},pre[101]={0},a[101]={0},d[101]={0},flow=0,cost=0,point[101]={0},next[20000]={0},tot; bool visit[101]={false}; void add(int i,int j,int va,int co) { ++tot;next[tot]=point[i];point[i]=tot; edge[tot].st=i;edge[tot].en=j;edge[tot].va=va;edge[tot].co=co; ++tot;next[tot]=point[j];point[j]=tot; edge[tot].st=j;edge[tot].en=i;edge[tot].va=0;edge[tot].co=-co; } bool work(int s,int t) { int head,tail,i,j,u,stan; memset(d,127,sizeof(d)); memset(visit,false,sizeof(visit)); memset(pre,0,sizeof(pre)); d[s]=0;visit[s]=true;stan=a[s]=d[0]; head=0;tail=1;que[tail]=s; while(head!=tail) { head=head%len+1; u=que[head]; visit[u]=false; for (i=point[u];i!=0;i=next[i]) { if (edge[i].va>0&&d[edge[i].en]>d[u]+edge[i].co) { d[edge[i].en]=d[u]+edge[i].co; pre[edge[i].en]=i; a[edge[i].en]=min(a[u],edge[i].va); if (!visit[edge[i].en]) { visit[edge[i].en]=true; tail=tail%len+1; que[tail]=edge[i].en; } } } } if (d[t]==stan) return false; flow+=a[t]; cost+=d[t]*a[t]; for (u=t;u!=s;u=edge[pre[u]].st) { edge[pre[u]].va-=a[t]; edge[pre[u]^1].va+=a[t]; } return true; } int main() { freopen("maxflowd.in","r",stdin); freopen("maxflowd.out","w",stdout); int n,s,t,i,j,va,co; scanf("%d%d%d",&n,&s,&t); tot=1; for (i=1;i<=n;++i) for (j=1;j<=n;++j) { scanf("%d%d",&va,&co); if (va) add(i,j,va,co); } while(work(s,t)); printf("%d\n",cost); fclose(stdin); fclose(stdout); }
codevs2436修车
题目大意:有m个工人,n个顾客,已知每个工人修相应车的时间,求最少平均等待时间。
思路:建三排点,从超级源点向每个顾客引一条边,容量为1,费用为0;从每个顾客向ci,j(i表示顾客,j表示工人)引一条边,容量为1,费用为0;从每个ci,j向kkj,k(j表示工人,k表示该工人倒数第k顺序修的车)引一条边,容量为1,费用为k*time[i][j](包括本身共k个人等time[i][j]的时间);从每个kkj,k向超级汇点引一条边,容量为1,费用为0。从超级源点向超级汇点做费用流,答案就是了。(当然也可以删掉第二排点,然后也可以做出解。)
建图的思路要明确,然后就很简单了。
#include<iostream> #include<cstdio> #include<cstring> #define len 1000000 using namespace std; struct use{ int st,en,va,co; }edge[100000]; int que[1000001]={0},d[2001]={0},pre[2001]={0},a[2001]={0},point[2001]={0},next[100000]={0},flow=0,cost=0, tot,ti[61][10]={0},c[61][10]={0},kk[10][61]={0}; bool visit[2001]={false}; void add(int i,int j,int va,int co) { ++tot;next[tot]=point[i];point[i]=tot; edge[tot].st=i;edge[tot].en=j;edge[tot].va=va;edge[tot].co=co; ++tot;next[tot]=point[j];point[j]=tot; edge[tot].st=j;edge[tot].en=i;edge[tot].va=0;edge[tot].co=-co; } bool work(int s,int t) { int head,tail,u,i,j,stan; memset(visit,false,sizeof(visit)); memset(d,127,sizeof(d));stan=d[0]; memset(a,0,sizeof(a)); memset(pre,0,sizeof(pre)); head=0;tail=1;que[tail]=s;visit[s]=true;d[s]=0;a[s]=stan; while(head!=tail) { head=head%len+1; u=que[head];visit[u]=false; for (i=point[u];i!=0;i=next[i]) { if (edge[i].va>0&&d[edge[i].en]>d[u]+edge[i].co) { d[edge[i].en]=d[u]+edge[i].co; a[edge[i].en]=min(a[u],edge[i].va); pre[edge[i].en]=i; if (!visit[edge[i].en]) { tail=tail%len+1;que[tail]=edge[i].en;visit[edge[i].en]=true; } } } } if (d[t]==stan) return false; flow+=a[t]; cost+=d[t]*a[t]; for (u=t;u!=s;u=edge[pre[u]].st) { edge[pre[u]].va-=a[t]; edge[pre[u]^1].va+=a[t]; } return true; } int main() { int n,m,i,j,k,dian; double ans; scanf("%d%d",&m,&n); tot=1; for (i=1;i<=n;++i) for (j=1;j<=m;++j) scanf("%d",&ti[i][j]); 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*ti[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)); ans=cost*1.0/n; printf("%0.2f\n",ans); }
cogs1366美食节(noi2012)
题目大意:同修车,只是改大了范围,每种菜可能有多道。
思路:同样建三排点,只是源点到第一排点的容量为p[i],第一排点到第二排点容量为p[i],其余不变,这样能过6个点,4个tle。
于是有一个优化,对于第三排点,如果倒数第k道菜没选好,那么第k+1道菜也没有安排,所以我们就在每个厨师的第k道菜安排好后,在加第k+1道菜的点和相应的边(每次增广之后。一定要注意加那些边,这里出了好久的问题。。。)
#include<iostream> #include<cstdio> #include<cstring> #define len 100000 using namespace std; struct use{ int st,en,va,co; }edge[1000000]; int que[100001]={0},d[100000]={0},pre[100000]={0},a[100000]={0},point[100000]={0},next[1000000]={0},flow=0,cost=0, tot,ti[41][101]={0},c[41][101]={0},p[41],lev[101]={0},dian,n,m,ji[100000]={0},sum=0,tt; bool visit[100000]={false}; void add(int i,int j,int va,int co) { ++tot;next[tot]=point[i];point[i]=tot; edge[tot].st=i;edge[tot].en=j;edge[tot].va=va;edge[tot].co=co; ++tot;next[tot]=point[j];point[j]=tot; edge[tot].st=j;edge[tot].en=i;edge[tot].va=0;edge[tot].co=-co; } bool work(int s,int t) { int head,tail,u,i,j,stan; memset(visit,false,sizeof(visit)); memset(d,127,sizeof(d));stan=d[0]; head=0;tail=1;que[tail]=s;visit[s]=true;d[s]=0;a[s]=stan; while(head!=tail) { head=head%len+1; u=que[head];visit[u]=false; for (i=point[u];i!=0;i=next[i]) { if (edge[i].va>0&&d[edge[i].en]>d[u]+edge[i].co) { d[edge[i].en]=d[u]+edge[i].co; a[edge[i].en]=min(a[u],edge[i].va); pre[edge[i].en]=i; if (!visit[edge[i].en]) { tail=tail%len+1;que[tail]=edge[i].en;visit[edge[i].en]=true; } } } } if (d[t]==stan) return false; flow+=a[t]; cost+=d[t]*a[t]; for (u=t;u!=s;u=edge[pre[u]].st) { edge[pre[u]].va-=a[t]; edge[pre[u]^1].va+=a[t]; } int cc=ji[edge[pre[t]].st]; ++lev[cc];++dian; ji[dian]=cc; for (i=1;i<=n;++i) add(c[i][cc],dian,1,lev[cc]*ti[i][cc]); add(dian,tt,1,0); return true; } int main() { freopen("noi12_delicacy.in","r",stdin); freopen("noi12_delicacy.out","w",stdout); int i,j,k; scanf("%d%d",&n,&m); tot=1; for (i=1;i<=n;++i) { scanf("%d",&p[i]); sum+=p[i]; } for (i=1;i<=n;++i) for (j=1;j<=m;++j) scanf("%d",&ti[i][j]); for (i=1;i<=n;++i) add(0,i,p[i],0); dian=n; for (i=1;i<=n;++i) for (j=1;j<=m;++j) { ++dian; c[i][j]=dian; add(i,dian,p[i],0); } for (i=1;i<=n;++i) for (j=1;j<=m;++j) { ji[dian+j]=j;lev[j]=1; add(c[i][j],dian+j,1,ti[i][j]); } dian+=m; ++dian; for (j=1;j<=m;++j) add(dian-1-m+j,dian,1,0); tt=dian; while(work(0,tt)); printf("%d\n",cost); fclose(stdin); fclose(stdout); }
cogs28||bzoj1497 最大获利
题目大意:最大权闭合子图模板题
思路:建两排点,由每一个用户群向中转站连有向边,用户群的点权为ci,中转站的点权为-pi。然后就是最大权闭合子图的部分了,从S向正权的点连容量为权的边,从负权向T连容量为|权|的边,原图中的边容量都为正无穷,然后跑最大流,所有正点权和-最大流就是答案了(这里其实是最小割的思路)。
(突然发现自己从来没有用过当前弧cur优化,加了当前弧优化之后,竟然快了。(无)。(数)。(倍)!!!)
#include<iostream> #include<cstdio> #define stan 2100000000LL using namespace std; struct use{ int st,en,va; }edge[500000]; int pp[5001]={0},mi[50001]={0},tot,point[56000]={0},next[500000]={0},dis[56000]={0},gap[56000]={0},pre[56000]={0}, cur[56000]={0},n,m; void add(int stt,int enn,int vaa) { ++tot; next[tot]=point[stt];point[stt]=tot;edge[tot].st=stt;edge[tot].en=enn;edge[tot].va=vaa; ++tot; next[tot]=point[enn];point[enn]=tot;edge[tot].st=enn;edge[tot].en=stt;edge[tot].va=0; } int sap(int stt,int enn) { int u,i,j,ans=0; bool f=false; for (i=1;i<=n+m+1;++i) cur[i]=point[i]; gap[0]=enn-stt+1;u=stt; while(dis[enn]<enn-stt+1) { f=false; for (i=cur[u];i!=0;i=next[i]) { if (dis[edge[i].en]+1==dis[u]&&edge[i].va>0) { cur[u]=i; f=true;break; } } if (f) { pre[edge[i].en]=i;u=edge[i].en; if (u==enn) { j=stan; for (i=u;i!=stt;i=edge[pre[i]].st) j=min(j,edge[pre[i]].va); ans+=j; for (i=u;i!=stt;i=edge[pre[i]].st) { edge[pre[i]].va-=j; edge[pre[i]^1].va+=j; } u=stt; } } else { --gap[dis[u]]; if (gap[dis[u]]==0) return ans; j=stan; for (i=point[u];i!=0;i=next[i]) if (edge[i].va) j=min(j,dis[edge[i].en]); dis[u]=j+1; cur[u]=point[u]; ++gap[dis[u]]; if (u!=stt) u=edge[pre[u]].st; } } return ans; } int main() { freopen("profit.in","r",stdin); freopen("profit.out","w",stdout); int i,j,ai,bi,ci,ans=0; tot=1; scanf("%d%d",&n,&m); for (i=1;i<=n;++i) { scanf("%d",&pp[i]); add(i,n+m+1,pp[i]); } for (i=1;i<=m;++i) { mi[i]=n+i; scanf("%d%d%d",&ai,&bi,&ci); add(0,mi[i],ci);add(mi[i],ai,stan);add(mi[i],bi,stan); ans+=ci; } i=sap(0,n+m+1); ans-=i; printf("%d\n",ans); fclose(stdin); fclose(stdout); }
cogs420 SDOI2009晨跑
题目大意:无交叉点的费用流。
思路:拆点,然后就可以保证无交叉点,然后就费用流。。。
#include<iostream> #include<cstdio> #include<cstring> #define len 10000000LL using namespace std; struct use{ int st,en,va,co; }edge[100000]; int next[100000]={0},point[401]={0},pre[401]={0},a[401]={0},d[401]={0},n,m,tot=0,day=0,cost=0,flow=0, que[10000001]={0},qian[201]={0},hou[201]={0}; bool visit[401]={false}; void add(int stt,int enn,int vaa,int coo) { ++tot;next[tot]=point[stt];point[stt]=tot; edge[tot].st=stt;edge[tot].en=enn;edge[tot].va=vaa;edge[tot].co=coo; ++tot;next[tot]=point[enn];point[enn]=tot; edge[tot].st=enn;edge[tot].en=stt;edge[tot].va=0;edge[tot].co=-coo; } bool work(int stt,int enn) { int head,tail,i,j,x; memset(visit,false,sizeof(visit)); memset(d,127,sizeof(d)); memset(a,127,sizeof(a)); memset(pre,0,sizeof(pre)); head=0;tail=1;que[tail]=stt;visit[stt]=true;d[stt]=0; while(head!=tail) { head=head%len+1; x=que[head];visit[x]=false; for (i=point[x];i!=0;i=next[i]) { if (edge[i].va>0&&d[edge[i].en]>d[x]+edge[i].co) { d[edge[i].en]=d[x]+edge[i].co; a[edge[i].en]=min(a[x],edge[i].va); pre[edge[i].en]=i; if (!visit[edge[i].en]) { visit[edge[i].en]=true; tail=tail%len+1; que[tail]=edge[i].en; } } } } if (d[enn]==d[0]) return false; flow+=a[enn]; cost+=a[enn]*d[enn]; for (i=enn;i!=stt;i=edge[pre[i]].st) { edge[pre[i]].va-=a[enn]; edge[pre[i]^1].va+=a[enn]; } return true; } int main() { freopen("run.in","r",stdin); freopen("run.out","w",stdout); int i,j,a,b,c; scanf("%d%d",&n,&m); qian[1]=hou[1]=1;qian[n]=hou[n]=2*n-2;tot=1; for (i=2;i<n;++i) { qian[i]=i*2-2;hou[i]=i*2-1; } for (i=2;i<n;++i) add(qian[i],hou[i],1,0); for (i=1;i<=m;++i) { scanf("%d%d%d",&a,&b,&c); add(hou[a],qian[b],1,c); } while(work(qian[1],hou[n])) ++day; printf("%d %d\n",day,cost); fclose(stdin); fclose(stdout); }
cogs664 SDOI2010星际竞速
题目大意:貌似是个很像路径覆盖的东西,只是有一些费用,然后求路径覆盖方案中费用最小的。
思路:这里的无向边一定是有向边,毕竟只能从小到大。一开始想不拆点,然后做,但是发现一条路径上的co与流量有关,而流量却不一定为1,所以可能一条路的co加了多遍。就这样卡住了,就这样陷入了死循环。。。后来发现了拆点,因为每一个点都经过一次,所以我们用这个点'表示它经过了,向汇点连边;而它经过的方式有两种,一种是通过之前的点过来,一种是通过跳跃过来,对于跳跃的就是从超级源点跳过来,所以就连出两种边;对于这个点能连出去的话,就用点连向所有的点',又出现一种边。所有边的容量都为1,费用相应的。。。然后费用流就可以了。(一定要掌握拆点这些神奇的技巧)
#include<iostream> #include<cstdio> #include<cstring> #define len 10000000LL using namespace std; struct use{ int st,en,va,co; }edge[100000]; int que[10000001]={0},a[2000]={0},d[2000]={0},pre[2000]={0},next[100000]={0},point[2000]={0},tot=0,n,m,cost; bool visit[2000]={false}; void add(int stt,int enn,int vaa,int coo) { ++tot;next[tot]=point[stt];point[stt]=tot; edge[tot].st=stt;edge[tot].en=enn;edge[tot].va=vaa;edge[tot].co=coo; ++tot;next[tot]=point[enn];point[enn]=tot; edge[tot].st=enn;edge[tot].en=stt;edge[tot].va=0;edge[tot].co=-coo; } bool work(int stt,int enn) { int head,tail,i,j,x; memset(pre,0,sizeof(pre)); memset(a,127,sizeof(a)); memset(d,127,sizeof(d)); memset(visit,false,sizeof(visit)); head=0;tail=1;que[1]=stt;visit[stt]=true;d[stt]=0; while(head!=tail) { head=head%len+1; x=que[head];visit[x]=false; for (i=point[x];i!=0;i=next[i]) { if (edge[i].va>0&&d[edge[i].en]>d[x]+edge[i].co) { d[edge[i].en]=d[x]+edge[i].co; a[edge[i].en]=min(edge[i].va,a[x]); pre[edge[i].en]=i; if (!visit[edge[i].en]) { visit[edge[i].en]=true; tail=tail%len+1; que[tail]=edge[i].en; } } } } if (d[enn]==d[1999]) return false; cost+=d[enn]*a[enn]; for (i=enn;i!=stt;i=edge[pre[i]].st) { edge[pre[i]].va-=a[enn]; edge[pre[i]^1].va+=a[enn]; } return true; } int main() { freopen("starrace.in","r",stdin); freopen("starrace.out","w",stdout); int n,m,i,j,ai,s,t,wi; tot=1; scanf("%d%d",&n,&m); for (i=1;i<=n;++i) { scanf("%d",&ai); add(0,i,1,0); add(0,i+n,1,ai); add(i+n,n*2+1,1,0); } for (i=1;i<=m;++i) { scanf("%d%d%d",&s,&t,&wi); if (s>t) swap(s,t); add(s,t+n,1,wi); } while(work(0,2*n+1)); printf("%d\n",cost); fclose(stdin); fclose(stdout); }
cogs728最小路径覆盖
题目大意:最小路径覆盖模板题,要输出路径。
思路:题目中竟然给出了建图。。。把每个点都拆成两个,从超级源点向前一个点连边①,从后一个点向汇点连边②,从起点的前一个点向终点的后一个点连边③,容量都是1,然后跑最大流,n-最大流就是答案了(其实最大流就是路径中的那些边的个数)。对于路径来说,每一条流量为0的边③都是路径上的,我们穷举路径的起点,一个路径的起点一定没有没被访问过得别的边③流量为0且终点为起点的后一个点,所以我们找点的时候judge一下,然后从起点往后递归这输出一下就可以了,因为一个点只能有一个终点(只访问一遍),所以不用担心多条路径。
貌似可以用匈牙利算法直接做,小小的学习了一下匈牙利算法(看到一篇博文里说:“匈牙利算法就是‘有机会就上,没机会创造机会上’。”),发现会简单一些。
#include<iostream> #include<cstdio> using namespace std; struct use{ int st,en,va,co; }edge[20000]; int point[500]={0},next[20000]={0},pre[500]={0},dis[500]={0},gap[500]={0},tot,cur[500]={0},n; bool visit[500]={false}; void add(int stt,int enn,int vaa) { ++tot;next[tot]=point[stt];point[stt]=tot; edge[tot].st=stt;edge[tot].en=enn;edge[tot].va=vaa; ++tot;next[tot]=point[enn];point[enn]=tot; edge[tot].st=enn;edge[tot].en=stt;edge[tot].va=0; } int sap(int stt,int enn) { int i,j,u,ans=0; bool f=false; gap[0]=enn-stt+1;u=stt; for (i=stt;i<=enn;++i) cur[i]=point[i]; while(dis[stt]<enn-stt+1) { f=false; for (i=cur[u];i!=0;i=next[i]) { if (edge[i].va>0&&dis[edge[i].en]+1==dis[u]) { cur[u]=i;f=true;break; } } if (f) { pre[edge[i].en]=i;u=edge[i].en; if (u==enn) { j=2100000000; for (i=u;i!=stt;i=edge[pre[i]].st) j=min(j,edge[pre[i]].va); ans+=j; for (i=u;i!=stt;i=edge[pre[i]].st) { edge[pre[i]].va-=j; edge[pre[i]^1].va+=j; } u=stt; } } else { --gap[dis[u]]; if (gap[dis[u]]==0) return ans; j=enn-stt+1; for (i=point[u];i!=0;i=next[i]) if (edge[i].va>0) if (dis[edge[i].en]<j) j=dis[edge[i].en]; dis[u]=j+1;cur[u]=point[u]; ++gap[dis[u]]; if (u!=stt) u=edge[pre[u]].st; } } return ans; } bool judge(int j) { int i; for (i=point[j+n];i!=0;i=next[i]) { if (edge[i].st>0&&edge[i].st<=n) if (!visit[edge[i].st]&&edge[i].va==0) return false; } return true; } void print(int j) { int i; visit[j]=true;printf("%d ",j); for (i=point[j];i!=0;i=next[i]) { if (edge[i].en>n&&edge[i].en<=2*n) if (edge[i].va==0) print(edge[i].en-n); } } int main() { freopen("path3.in","r",stdin); freopen("path3.out","w",stdout); int m,i,j,s,t,ans; tot=1; scanf("%d%d",&n,&m); for (i=1;i<=n;++i) { add(0,i,1);add(i+n,2*n+1,1); } for (i=1;i<=m;++i) { scanf("%d%d",&s,&t); add(s,t+n,1); } ans=n-sap(0,2*n+1); for (i=1;i<=ans;++i) { for (j=1;j<=n;++j) if (!visit[j]&&judge(j)) break; print(j);printf("\n"); } printf("%d\n",ans); fclose(stdin); fclose(stdout); }
#include<iostream> #include<cstdio> #include<cstring> using namespace std; int map[300][300]={0},match[300]={0},go[300]={0}; bool visit[300]={false}; bool find(int i) { int j; for (j=1;j<=map[i][0];++j) { if (!visit[map[i][j]]) { visit[map[i][j]]=true; if (!match[map[i][j]]||find(match[map[i][j]])) { match[map[i][j]]=i;go[i]=map[i][j]; return true; } } } return false; } int main() { freopen("path3.in","r",stdin); freopen("path3.out","w",stdout); int n,m,i,j,s,t,ans=0; scanf("%d%d",&n,&m); for (i=1;i<=m;++i) { scanf("%d%d",&s,&t); ++map[s][0];map[s][map[s][0]]=t; } for (i=1;i<=n;++i) { memset(visit,false,sizeof(visit)); if(!find(i)) ++ans; } for (i=1;i<=n;++i) { if (!match[i]) { j=i; do{ printf("%d ",j); j=go[j]; }while(j); printf("\n"); } } printf("%d\n",ans); fclose(stdin); fclose(stdout); }
cogs738数字梯形
题目大意:找m条路线,分别满足三种条件,并输出最大和(有点像数字三角形)。
思路:根据三种条件建三次图,注意的是这m条路径的起点不能相同,正好m条路径的起点分别为第一行的m个点,然后跑最大费用流就可以了。
#include<iostream> #include<cstdio> #include<cstring> #define len 1000000 using namespace std; struct use{ int st,en,va,co; }edge[10000]={0}; int tot,next[10000]={0},point[5000]={0},dian[21][40][2]={0},map[21][40]={0},que[1000001],dis[5000]={0},a[5000]={0},pre[5000]={0},ans; bool visit[5000]={false}; void add(int st,int en,int va,int co) { ++tot;next[tot]=point[st];point[st]=tot; edge[tot].st=st;edge[tot].en=en;edge[tot].va=va;edge[tot].co=co; ++tot;next[tot]=point[en];point[en]=tot; edge[tot].st=en;edge[tot].en=st;edge[tot].va=0;edge[tot].co=-co; } bool sap(int st,int en) { int head,tail,i,j,x,y; memset(dis,128,sizeof(dis)); memset(a,127,sizeof(a)); memset(visit,false,sizeof(visit)); dis[st]=0;head=0;tail=1;que[1]=st;visit[st]=true; while(head!=tail) { head=head%len+1; x=que[head];visit[x]=false; for (y=point[x];y!=0;y=next[y]) { if (edge[y].va>0&&dis[edge[y].en]<edge[y].co+dis[x]) { dis[edge[y].en]=edge[y].co+dis[x]; a[edge[y].en]=min(a[x],edge[y].va); pre[edge[y].en]=y; if (!visit[edge[y].en]) { visit[edge[y].en]=true;tail=tail%len+1;que[tail]=edge[y].en; } } } } if (dis[en]==dis[0]) return false; ans+=dis[en]*a[en]; for (x=en;x!=st;x=edge[pre[x]].st) { edge[pre[x]].va-=a[en]; edge[pre[x]^1].va+=a[en]; } return true; } int main() { freopen("digit.in","r",stdin); freopen("digit.out","w",stdout); int n,m,i,j,totd=0,enn; scanf("%d%d",&m,&n); totd=2; for (i=1;i<=n;++i) for (j=1;j<m+i;++j) { scanf("%d",&map[i][j]); dian[i][j][0]=++totd;dian[i][j][1]=++totd; } enn=++totd; tot=1;add(1,2,m,0); for (i=1;i<=m;++i) add(2,dian[1][i][0],1,0); for (i=1;i<=n;++i) for (j=1;j<m+i;++j) { add(dian[i][j][0],dian[i][j][1],1,map[i][j]); if (i<n) { add(dian[i][j][1],dian[i+1][j][0],1,0); add(dian[i][j][1],dian[i+1][j+1][0],1,0); } else add(dian[i][j][1],enn,1,0); } ans=0; while(sap(1,enn)); printf("%d\n",ans); memset(next,0,sizeof(next)); memset(point,0,sizeof(point)); memset(edge,0,sizeof(edge)); tot=1;add(1,2,m,0); for (i=1;i<=m;++i) add(2,dian[1][i][0],1,0); for (i=1;i<=n;++i) for (j=1;j<m+i;++j) { add(dian[i][j][0],dian[i][j][1],m,map[i][j]); if (i<n) { add(dian[i][j][1],dian[i+1][j][0],1,0); add(dian[i][j][1],dian[i+1][j+1][0],1,0); } else add(dian[i][j][1],enn,m,0); } ans=0; while(sap(1,enn)); printf("%d\n",ans); memset(next,0,sizeof(next)); memset(point,0,sizeof(point)); memset(edge,0,sizeof(edge)); tot=1;add(1,2,m,0); for (i=1;i<=m;++i) add(2,dian[1][i][0],1,0); for (i=1;i<=n;++i) for (j=1;j<m+i;++j) { add(dian[i][j][0],dian[i][j][1],m,map[i][j]); if (i<n) { add(dian[i][j][1],dian[i+1][j][0],m,0); add(dian[i][j][1],dian[i+1][j+1][0],m,0); } else add(dian[i][j][1],enn,m,0); } ans=0; while(sap(1,enn)); printf("%d\n",ans); fclose(stdin); fclose(stdout); }
cogs746 骑士共存
题目大意:在n*n的棋盘上放马,要求互不攻击,求最多能放多少个。
思路:为了构建二分图,我们把棋盘黑白染色,黑色格子只能向白色连边(马跳跃的性质),然后找最大匹配,用总点数(无障碍的)-最大匹配数就是答案。
注意:二分图,把棋盘分成两部分的思想。(这就是二分图最大独立子集问题,证明比较简单,我们可以看做删掉最少的点,使剩下的点之间没有连边,即用最小覆盖的答案,而最小覆盖又等于最大匹配,所以就可以表示成总点数-最大匹配数了。)
#include<iostream> #include<cstdio> #include<cstring> using namespace std; int db[201][201],match[40001]={0},next[700000]={0},point[40001]={0},en[700000]={0},tot=0, dx[8]={-1,-2,-2,-1,1,2,2,1},dy[8]={-2,-1,1,2,-2,-1,1,2}; bool visit[40001]={false}; void add(int st,int enn) { ++tot;next[tot]=point[st];point[st]=tot;en[tot]=enn; ++tot;next[tot]=point[enn];point[enn]=tot;en[tot]=st; } bool find(int x) { int i; for (i=point[x];i!=0;i=next[i]) { if (!visit[en[i]]) { visit[en[i]]=true; if (!match[en[i]]||find(match[en[i]])) { match[en[i]]=x;return true; } } } return false; } int main() { freopen("knight.in","r",stdin); freopen("knight.out","w",stdout); int i,j,n,m,totd=0,x,y,t,ans=0; scanf("%d%d",&n,&m); for (i=1;i<=n;++i) for (j=1;j<=n;++j) db[i][j]=++totd; for (i=1;i<=m;++i) { scanf("%d%d",&x,&y); db[x][y]=0; } for (i=1;i<=n;++i) for (j=1;j<=n;++j) { if (db[i][j]&&((i+j)%2==0)) { for (t=0;t<8;++t) { x=i+dx[t];y=j+dy[t]; if (x<1||x>n) continue; if (y<1||y>n) continue; if (!db[x][y]) continue; add(db[i][j],db[x][y]); } } } for (i=1;i<=n;++i) for (j=1;j<=n;++j) { if (!db[i][j]||(i+j)%2==1) continue; memset(visit,false,sizeof(visit)); if (find(db[i][j])) ++ans; } ans=totd-ans-m; printf("%d\n",ans); fclose(stdin); fclose(stdout); }
cogs461餐巾||bzoj1221软件开发
题目大意:我们知道n天里每天需要的毛巾数,我们可以新买毛巾(花费f),也可以把一天用过的毛巾消毒再用(两种方法:要a天的花费fa;要b天的花费fb),然后求满足n天要求的最小花费。
思路:纠结了好久的题目。其实很像之前做过的星际竞速。对于一天的毛巾我们有新毛巾和旧毛巾两排点,为满足新毛巾的要求,我们从源点向这排点连流量正无穷,花费f的边(代表新买的);从旧毛巾里a+1或b+1天前的点连边为流量正无穷,花费fa或fb的边(代表洗的毛巾);从新毛巾向汇点连流量ni[i],花费0的边,满足每天有这么多天毛巾用。为了同时更新旧毛巾,从源点向旧毛巾这排点连流量ni[i]花费0的边,表示这一天用过的毛巾;从上一点的旧毛巾连下来,表示原来的旧毛巾可以留到今天(说成隔几天在洗,遭到了大神的鄙视。。。其实有点像个优化,如果不连这条边的话,就要从这个点向新毛巾里的i+a+1...i+a+j(i+a+j==n)、i+b+1...i+b+j(i+b+j==n)连边了,复杂度将大大提高。)然后跑费用流就可以了。
注意:网络流的构图十分重要,对于很多相似的情况要能熟练应用和理解。
#include<iostream> #include<cstdio> #include<cstring> #define len 1000000 #define inf 2100000000LL using namespace std; struct use{ int st,en,va,co; }edge[20001]={0}; int point[2010]={0},next[20001]={0},dis[2010],a[2010],pre[2010]={0},que[1000001]={0},ni[2010],ans=0,tot; bool visit[2010]={false}; void add(int st,int en,int va,int co) { ++tot;next[tot]=point[st];point[st]=tot; edge[tot].st=st;edge[tot].en=en;edge[tot].va=va;edge[tot].co=co; ++tot;next[tot]=point[en];point[en]=tot; edge[tot].st=en;edge[tot].en=st;edge[tot].va=0;edge[tot].co=-co; } bool work(int st,int en) { int head,tail,x,y; memset(visit,false,sizeof(visit)); memset(dis,127,sizeof(dis)); memset(pre,0,sizeof(pre)); head=0;tail=1;que[tail]=st;visit[st]=true;a[st]=inf;dis[st]=0; while(head!=tail) { head=head%len+1; x=que[head];visit[x]=false; for (y=point[x];y;y=next[y]) { if (edge[y].va&&dis[x]+edge[y].co<dis[edge[y].en]) { dis[edge[y].en]=edge[y].co+dis[x]; pre[edge[y].en]=y; a[edge[y].en]=min(a[x],edge[y].va); if (!visit[edge[y].en]) { visit[edge[y].en]=true; tail=tail%len+1;que[tail]=edge[y].en; } } } } if (dis[en]==dis[2009]) return false; ans+=a[en]*dis[en]; for (x=en;x!=st;x=edge[pre[x]].st) { edge[pre[x]].va-=a[en]; edge[pre[x]^1].va+=a[en]; } return true; } int main() { int n,a,b,f,fa,fb,en,i; scanf("%d%d%d%d%d%d",&n,&a,&b,&f,&fa,&fb); en=2*n+1;tot=1; for (i=1;i<=n;++i) scanf("%d",&ni[i]); for (i=1;i<=n;++i) { add(0,i*2-1,ni[i],f); add(0,i*2,ni[i],0); if (i<n) add(i*2,(i+1)*2,inf,0); if (i+a+1<=n) add(i*2,(i+a+1)*2-1,inf,fa); if (i+b+1<=n) add(i*2,(i+b+1)*2-1,inf,fb); add(i*2-1,en,ni[i],0); } while(work(0,en)); printf("%d\n",ans); }
cogs396魔术球问题
题目大意:按顺序放入1、2、3...的球在n个柱子上,使相邻的两个球和为完全平方数,问最多能放多少个。
思路:当做贪心来写的。后来明白了怎么跟网络流扯上关系,我们穷举答案,然后将1...ans内能构成相邻的数从小到大连一条边,然后就是最小路径覆盖了,如果路径数<=n就是合理的,找到第一个超过n的ans,ans-1就是答案了,可以用网络流也可以用匈牙利做。不过原题好像要输出方案,不知道是不是要special judge。
(好像看到一个黑心的公式:(n^2+2*n-1)div 2)(自己写的贪心,所以就不贴code了。。。)
cogs729圆桌聚餐
题目大意:m个单位,n张桌子,要求同一单位的人不在同一桌上,如果能安排下就输出1和方案,如果不能就输出0。
思路:最大流的做法。建两排点,从源点向单位连边为单位的人,从桌子向汇点连边为桌子的容量,每个单位向每个桌子连边为1,跑最大流,如果等于所有单位的人数,就是有解,然后找一下残余网络中一二排点之间流量为0的边就是一个安排,然后相应的输出就可以了。
#include<iostream> #include<cstdio> #include<cstring> #define inf 2100000000 using namespace std; struct use{ int st,en,va; }edge[100000]={0}; int ri[151]={0},ci[271]={0},ni[271]={0},next[100000]={0},point[450]={0},cur[450]={0},n,m, gap[450]={0},pre[450]={0},dis[450]={0},tot; void add(int st,int en,int va) { ++tot;next[tot]=point[st];point[st]=tot; edge[tot].st=st;edge[tot].en=en;edge[tot].va=va; ++tot;next[tot]=point[en];point[en]=tot; edge[tot].st=en;edge[tot].en=st;edge[tot].va=0; } int sap(int st,int en) { int i,j,u,ans=0; bool f=false; u=st;gap[0]=en-st+1; for (i=0;i<=n+m+1;++i) cur[i]=point[i]; while(dis[st]<=en-st) { f=false; for (i=cur[u];i;i=next[i]) { if (edge[i].va&&dis[u]==dis[edge[i].en]+1) { cur[u]=i;f=true;break; } } if (f) { pre[edge[i].en]=i;u=edge[i].en; if (u==en) { j=inf; for (i=en;i!=st;i=edge[pre[i]].st) j=min(j,edge[pre[i]].va); ans+=j; for (i=en;i!=st;i=edge[pre[i]].st) { edge[pre[i]].va-=j; edge[pre[i]^1].va+=j; } u=st; } } else { --gap[dis[u]]; if (gap[dis[u]]==0) return ans; j=en-st+1; for (i=point[u];i;i=next[i]) if (edge[i].va) j=min(j,dis[edge[i].en]); dis[u]=j+1;cur[u]=point[u]; ++gap[dis[u]]; if (u!=st) u=edge[pre[u]].st; } } return ans; } int main() { freopen("roundtable.in","r",stdin); freopen("roundtable.out","w",stdout); int i,j,en,summ=0,ans; tot=1; scanf("%d%d",&m,&n); for (i=1;i<=m;++i) { scanf("%d",&ri[i]); summ+=ri[i]; } for (i=1;i<=n;++i) { scanf("%d",&ci[i]); ni[i]=m+i; } en=n+m+1; for (i=1;i<=m;++i) add(0,i,ri[i]); for (i=1;i<=n;++i) add(ni[i],en,ci[i]); for (i=1;i<=m;++i) for (j=1;j<=n;++j) add(i,ni[j],1); ans=sap(0,en); if (ans!=summ) printf("0\n"); else { printf("1\n"); for (i=1;i<=m;++i) { for (j=point[i];j;j=next[j]) if (!edge[j].va) printf("%d ",edge[j].en-m); printf("\n"); } } fclose(stdin); fclose(stdout); }
cogs727太空飞行计划
题目大意:有m项实验,n个器材,一直每个实验的价值和器材的花费,以及每个实验所需要的器材,求可获得的最大价值。
思路:最大权闭合子图。读入比较麻烦,然后就是建图跑最大权闭合子图,最后又是一个麻烦的东西:输出方案。基于以前对最大权闭合子图做法的理解,发现如果一个点和源点的连边仍有流量,就说明这个点在答案的集合里,然后我们就要找到所有这样的点以及他们的器材(dfs了一下,这样能把走过的所有边都找到),然后就是按顺序从小到大输出就可以了。
注意:最近做到一些网络流输出方案的题目,要分析流量和答案之间的关系,然后找到相应的方案。
#include<iostream> #include<cstdio> #include<cstring> #define inf 2100000000 using namespace std; struct use{ int st,en,va; }edge[50000]={0}; int dis[210]={0},gap[210]={0},cur[210]={0},pre[210]={0},ni[101]={0},next[50000]={0},point[210]={0}, exp[101][101]={0},cm[101]={0},cn[101]={0},tot; bool visit[210]={0}; void add(int st,int en,int va) { ++tot;next[tot]=point[st];point[st]=tot; edge[tot].st=st;edge[tot].en=en;edge[tot].va=va; ++tot;next[tot]=point[en];point[en]=tot; edge[tot].st=en;edge[tot].en=st;edge[tot].va=0; } int sap(int st,int en) { int i,j,u,ans=0; bool f=false; for (i=st;i<=en;++i) cur[i]=point[i]; gap[0]=en-st+1;u=st; while(dis[st]<=en-st) { f=false; for (i=cur[u];i;i=next[i]) { if (edge[i].va&&dis[edge[i].en]+1==dis[u]) { f=true;cur[u]=i;break; } } if (f) { pre[edge[i].en]=i;u=edge[i].en; if (u==en) { j=inf; for (i=en;i!=st;i=edge[pre[i]].st) j=min(j,edge[pre[i]].va); ans+=j; for (i=en;i!=st;i=edge[pre[i]].st) { edge[pre[i]].va-=j; edge[pre[i]^1].va+=j; } u=st; } } else { --gap[dis[u]];if (gap[dis[u]]==0) return ans; j=en-st+1; for (i=point[u];i;i=next[i]) if (edge[i].va) j=min(j,dis[edge[i].en]); dis[u]=j+1;++gap[dis[u]];cur[u]=point[u]; if (u!=st) u=edge[pre[u]].st; } } return ans; } void find(int x) { int i,j; visit[x]=true; for (i=point[x];i;i=next[i]) if (edge[i].va&&!visit[edge[i].en]) find(edge[i].en); } int main() { freopen("shuttle.in","r",stdin); freopen("shuttle.out","w",stdout); int n,m,i,j,ans=0,en; char ch; tot=1; scanf("%d%d",&m,&n); for (i=1;i<=m;++i) { scanf("%d",&cm[i]); j=0; while(scanf("%c",&ch)==1) { if (ch=='\r'||ch=='\n') { if (j!=0) { ++exp[i][0];exp[i][exp[i][0]]=j; } break; } if (ch==' '&&j!=0) { ++exp[i][0]; exp[i][exp[i][0]]=j; j=0; } else { if (ch!=' ') j=j*10+ch-'0'; } } } en=n+m+1; for (i=1;i<=n;++i) { scanf("%d",&cn[i]); ni[i]=i+m; } for (i=1;i<=m;++i) { add(0,i,cm[i]);ans+=cm[i]; for (j=1;j<=exp[i][0];++j) add(i,ni[exp[i][j]],inf); } for (i=1;i<=n;++i) add(ni[i],en,cn[i]); ans-=sap(0,en); find(0); for (i=1;i<=m;++i) if (visit[i]) printf("%d ",i); printf("\n"); for (i=1;i<=n;++i) if (visit[ni[i]]) printf("%d ",i); printf("\n%d\n",ans); fclose(stdin); fclose(stdout); }
cogs731最长递增子序列
题目大意:求最长子序列长度,以及原串中能取出多少个(一个元素只能用一遍),当第1个和第n个能用多次的时候能取出多少个。
思路:先dp求出第一个答案(竟然写错了这个交了两遍。。。)然后进行费用流,如果一次增广出的费用等于第一个答案,那么就累加答案,对于第二个和第三个的建图略有不同。第二个当中从源点向每个点、每个点向汇点、每个点向后面>=这个点的点连流量1费用1的边,然后跑最大费用;第三个当中如果是第一个点,就从源点向它连边流量为正无穷,如果为n就向汇点连边流量为正无穷,照样跑。
看到榜上大神的做法:根据第一次dp出来的答案,拆点,连边流量为1,保证一个点只用一次,如果f[i]==1,从s向i连边,如果f[i]==s1,就从i’向t连边,如果f[i]能由f[j]更新,就从j'点向i连边,跑出来的最大流就是第二个答案;在相应的改变流量就是第三个了。
#include<iostream> #include<cstdio> #include<cstring> #define inf 2100000000 #define len 1000000 using namespace std; struct use{ int st,en,va,co; }edge[500000]={0}; int dis[510]={0},a[502]={0},pre[502]={0},next[500000]={0},point[502]={0},aa[501]={0},f[501]={0}, que[1000001]={0},s1=0,tot; bool visit[502]={false}; void add(int st,int en,int va,int co) { ++tot;next[tot]=point[st];point[st]=tot; edge[tot].st=st;edge[tot].en=en;edge[tot].va=va;edge[tot].co=co; ++tot;next[tot]=point[en];point[en]=tot; edge[tot].st=en;edge[tot].en=st;edge[tot].va=0;edge[tot].co=-co; } bool sap(int st,int en,int &ss) { int x,y,head,tail; memset(visit,false,sizeof(visit)); memset(dis,128,sizeof(dis)); dis[st]=0;head=0;tail=1;que[tail]=st;visit[st]=true;a[st]=inf; while(head!=tail) { head=head%len+1; x=que[head];visit[x]=false; for (y=point[x];y;y=next[y]) { if (edge[y].va&&dis[x]+edge[y].co>dis[edge[y].en]) { dis[edge[y].en]=dis[x]+edge[y].co; pre[edge[y].en]=y; a[edge[y].en]=min(a[x],edge[y].va); if (!visit[edge[y].en]) { visit[edge[y].en]=true; tail=tail%len+1;que[tail]=edge[y].en; } } } } if (dis[en]==dis[509]) return false; if (dis[en]==s1) ++ss; for (x=en;x!=st;x=edge[pre[x]].st) { edge[pre[x]].va-=a[en]; edge[pre[x]^1].va+=a[en]; } return true; } int main() { freopen("alis.in","r",stdin); freopen("alis.out","w",stdout); int n,i,j,s2=0,s3=0; scanf("%d",&n); tot=1; for (i=1;i<=n;++i) { scanf("%d",&aa[i]); for (j=1;j<i;++j) if (aa[j]<=aa[i]&&f[j]>=f[i]) f[i]=f[j]; ++f[i]; s1=max(s1,f[i]); } printf("%d\n",s1); for (i=1;i<=n;++i) { add(0,i,1,1); for (j=i+1;j<=n;++j) if (aa[i]<=aa[j]) add(i,j,1,1); add(i,n+1,1,0); } while(sap(0,n+1,s2)); printf("%d\n",s2); memset(next,0,sizeof(next)); memset(point,0,sizeof(point)); tot=1; for (i=1;i<=n;++i) { if (i==1) add(0,i,inf,1); else add(0,i,1,1); for (j=i+1;j<=n;++j) if (aa[i]<=aa[j]) add(i,j,1,1); if (i==n) add(i,n+1,inf,0); else add(i,n+1,1,0); } while(sap(0,n+1,s3)); printf("%d\n",s3); fclose(stdin); fclose(stdout); }
cogs741负载平衡
题目大意:环状排列的n个仓库,每一个能向相邻两个输送货物,求n个仓库储量一致的时候最小的代价。
思路:延用了之前很多题目的思路,将这些点同化。求出平均数k之后用aa[i]-k,如果>0就从源点连边,如果<0就向汇点连边,流量就是相应的差费用0。相邻的两点之间连边流量正无穷,费用为1。然后跑费用流,就是答案了。(竟然写错了spfa,队首元素取出后一定要标记置反!!!)
#include<iostream> #include<cstdio> #include<cstring> #define len 1000000 #define inf 2100000000 using namespace std; struct use{ int st,en,va,co; }edge[10000]; int dis[110],a[110],pre[110]={0},que[1000001]={0},next[10000]={0},point[110]={0},aa[101]={0},tot,ans=0; bool visit[110]={false}; void add(int st,int en,int va,int co) { ++tot;next[tot]=point[st];point[st]=tot; edge[tot].st=st;edge[tot].en=en;edge[tot].va=va;edge[tot].co=co; ++tot;next[tot]=point[en];point[en]=tot; edge[tot].st=en;edge[tot].en=st;edge[tot].va=0;edge[tot].co=-co; } bool sap(int st,int en) { int x,y,head,tail; memset(visit,false,sizeof(visit)); memset(dis,127,sizeof(dis)); dis[st]=0;head=0;tail=1;que[1]=st;visit[st]=true;a[st]=dis[109]; while(head!=tail) { head=head%len+1; x=que[head];visit[x]=false; for (y=point[x];y;y=next[y]) { if (edge[y].va&&dis[edge[y].en]>dis[x]+edge[y].co) { dis[edge[y].en]=dis[x]+edge[y].co; a[edge[y].en]=min(a[x],edge[y].va); pre[edge[y].en]=y; if (!visit[edge[y].en]) { visit[edge[y].en]=true; tail=tail%len+1;que[tail]=edge[y].en; } } } } if (dis[en]==dis[109]) return false; ans+=dis[en]*a[en]; for (x=en;x!=st;x=edge[pre[x]].st) { edge[pre[x]].va-=a[en]; edge[pre[x]^1].va+=a[en]; } return true; } int main() { freopen("overload.in","r",stdin); freopen("overload.out","w",stdout); int n,i,j,k=0,en; scanf("%d",&n);en=n+1;tot=1; for (i=1;i<=n;++i) { scanf("%d",&aa[i]); k+=aa[i]; } k/=n; for (i=1;i<=n;++i) { aa[i]-=k; if (aa[i]>0) add(0,i,aa[i],0); if (aa[i]<0) add(i,en,-aa[i],0); if (i<n) { add(i,i+1,inf,1);add(i+1,i,inf,1); } else { add(1,n,inf,1);add(n,1,inf,1); } } while(sap(0,en)); printf("%d\n",ans); fclose(stdin); fclose(stdout); }
bzoj3993星际战争
题目大意:有n个敌人,m个武器,每种武器有特定的攻击敌人,伤害不同,求最短消灭所有敌人的时间。
思路:因为有小数,我们乘一个1000000,到整数上,然后二分答案,跑网络流(每种武器在二分时间的最多的伤害已知,如果到汇点满流,就是可行)。
注意longlong和int之间一些强转。。。
#include<iostream> #include<cstdio> #include<cstring> #define inf 1000000 #define linf 9223372036854775806LL using namespace std; struct use{ int st,en; long long va; }edge[10000]={0}; int a[100]={0},b[100]={0},tot,map[100][100],bb[100]={0},ba[100]={0},n,m,point[200]={0},next[10000]={0}, gap[200]={0},cur[200]={0},dis[200]={0},pre[200]={0}; void add(int st,int en,long long va) { ++tot;next[tot]=point[st];point[st]=tot; edge[tot].st=st;edge[tot].en=en;edge[tot].va=va; ++tot;next[tot]=point[en];point[en]=tot; edge[tot].st=en;edge[tot].en=st;edge[tot].va=0; } long long sap(int st,int en) { int i,j,u; long long minn,ans=0; bool f=false; memset(gap,0,sizeof(gap)); memset(dis,0,sizeof(dis)); memset(pre,0,sizeof(pre)); gap[0]=en-st+1;u=st; for (i=st;i<=en;++i) cur[i]=point[i]; while(dis[st]<en-st+1) { f=false; for (i=cur[u];i;i=next[i]) { if (edge[i].va&&dis[edge[i].en]+1==dis[u]) { f=true;cur[u]=i;break; } } if (f) { pre[edge[i].en]=i;u=edge[i].en; if (u==en) { minn=linf; for (i=en;i!=st;i=edge[pre[i]].st) minn=min(minn,edge[pre[i]].va); ans+=minn; for (i=en;i!=st;i=edge[pre[i]].st) { edge[pre[i]].va-=minn; edge[pre[i]^1].va+=minn; } u=st; } } else { --gap[dis[u]]; if (!gap[dis[u]]) return ans; minn=en-st+1; for (i=point[u];i;i=next[i]) if (edge[i].va&&dis[edge[i].en]<minn) minn=dis[edge[i].en]; cur[u]=point[u]; dis[u]=minn+1;++gap[dis[u]]; if (u!=st) u=edge[pre[u]].st; } } return ans; } bool judge(long long tim,int en) { int i,j; long long sum=0,k; tot=1; memset(point,0,sizeof(point)); memset(next,0,sizeof(next)); for (i=1;i<=m;++i) add(1,bb[i],tim*(long long)b[i]); for (i=1;i<=m;++i) for (j=1;j<=n;++j) if (map[i][j]) add(bb[i],ba[j],linf); for (j=1;j<=n;++j) { add(ba[j],en,(long long)inf*(long long)a[j]); sum+=(long long)inf*(long long)a[j]; } k=sap(1,en); if (k==sum) return true; else return false; } int main() { int i,j,sum=0,minn,en; long long ll,rr,mid; double ans; scanf("%d%d",&n,&m); for (i=1;i<=n;++i) { scanf("%d",&a[i]); sum+=a[i]; ba[i]=m+i+1; } minn=2100000000; for (j=1;j<=m;++j) { scanf("%d",&b[j]); minn=min(minn,b[j]); bb[j]=j+1; } en=2+m+n; for (i=1;i<=m;++i) for (j=1;j<=n;++j) scanf("%d",&map[i][j]); ll=0;rr=(long long)(sum/minn)*(long long)inf; while(ll<rr) { mid=(ll+rr)/2; if (judge(mid,en)) rr=mid; else ll=mid+1; } ans=ll*1.0/inf; printf("%.6f\n",ans); }
cogs410 noi2009植物大战僵尸
题目大意:n*m的方格内有植物,他们可以保护其他植物,也有收益或代价,求收益最大值。
思路:比较明显的最大权闭合子图,但是有一些细节问题。我们按保护关系连边的时候有可能会有环,我们得用拓扑序判一下环。同时有一个隐含条件就是后一列的植物保护同行前一列的植物。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define maxnode 1000 #define inf 2100000000 using namespace std; int point[maxnode]={0},next[1000000]={0},tot,cur[maxnode]={0},gap[maxnode]={0},dis[maxnode]={0},pre[maxnode]={0}, map[maxnode][maxnode]={0},wi[maxnode],id[50][50]={0},du[maxnode]={0},zhan[maxnode]={0},st[maxnode*2], en[1000000],va[1000000],n,m; bool visit[maxnode]={false}; void add(int stt,int enn,int vaa) { ++tot;next[tot]=point[stt];point[stt]=tot;en[tot]=enn;va[tot]=vaa;st[tot]=stt; ++tot;next[tot]=point[enn];point[enn]=tot;en[tot]=stt;va[tot]=0;st[tot]=enn; } int sap(int stt,int enn) { int i,j,u,ans=0; bool ff; u=stt;gap[0]=enn-stt+1; for (i=0;i<=n+m+1;++i) cur[i]=point[i]; while(dis[stt]<enn-stt+1) { ff=false; for (i=cur[u];i;i=next[i]) { if (va[i]&&dis[en[i]]+1==dis[u]) { ff=true;cur[u]=i;break; } } if (ff) { pre[en[i]]=i;u=en[i]; if (u==enn) { j=inf; for (i=u;i!=stt;i=st[pre[i]]) j=min(j,va[pre[i]]); ans+=j; for (i=u;i!=stt;i=st[pre[i]]) { va[pre[i]]-=j;va[pre[i]^1]+=j; } u=stt; } } else { --gap[dis[u]]; if (!gap[dis[u]]) return ans; j=enn-stt+1; for (i=point[u];i;i=next[i]) if (va[i]&&dis[en[i]]<j) j=dis[en[i]]; dis[u]=j+1;++gap[dis[u]]; cur[u]=point[u]; if (u!=stt) u=st[pre[u]]; } } return ans; } int main() { freopen("pvz.in","r",stdin); freopen("pvz.out","w",stdout); int i,j,k,w,x,y,top=0,sum=0; scanf("%d%d",&n,&m); for (i=0;i<n;++i) for (j=0;j<m;++j) { id[i][j]=i*m+j+1; scanf("%d%d",&wi[id[i][j]],&w); for (k=1;k<=w;++k) { scanf("%d%d",&x,&y); map[id[i][j]][++map[id[i][j]][0]]=x*m+y+1; ++du[x*m+y+1]; } if (j) { map[id[i][j]][++map[id[i][j]][0]]=id[i][j]-1; ++du[id[i][j]-1]; } } for (i=1;i<=n*m;++i) if (!du[i]) zhan[++top]=i; while(top) { i=zhan[top];visit[i]=true;--top; for (j=1;j<=map[i][0];++j) { --du[map[i][j]]; if (!du[map[i][j]]) zhan[++top]=map[i][j]; } } tot=1; for (i=1;i<=n*m;++i) { if (visit[i]) { if (wi[i]>0) { add(0,i,wi[i]);sum+=wi[i]; } if (wi[i]<0) add(i,n*m+1,-wi[i]); } } for (i=1;i<=n*m;++i) for (j=1;j<=map[i][0];++j) if (visit[i]&&visit[map[i][j]]) add(map[i][j],i,inf); printf("%d\n",sum-sap(0,n*m+1)); fclose(stdin); fclose(stdout); }
bzoj1001 bjoi2006狼抓兔子
题目大意:网格的边上有一些权值,求从(1,1)到(n,m)的最小割。
思路:这里的n、m都非常大,直接跑网络流肯定会tle,所以我们要根据这个题目的特殊性质进行优化(这一部分看了08年队爷的论文)。根据这个题目中的描述,这是一个平面图,我们建立这个平面图的对偶图(从s->t连一条边,就又多出一个平面,这个平面作为起点,无穷面作为终点)(对于一个两个面间的边,就把这两个面的点连边;对于一个面中的边,就从这个面的点连一条回边)。对偶图中有一些性质:(1)对偶图中的点是平面图中的面,对偶图中的面是平面图中的点,对偶图中的边数等于平面图的边数;(2)对偶图的最短路就是平面图中的最小割。所以我们只需要跑一遍最短路就可以了。
#include<iostream> #include<cstdio> #include<cstring> #define len 2000000 using namespace std; struct use{ int en,va; }edge[6000000]={0}; int point[2000010]={0},next[6000010]={0},dis[2000010],que[2000010]={0},tot=0; bool visit[2000010]={0}; void add(int st,int en,int va) { ++tot;next[tot]=point[st];point[st]=tot; edge[tot].en=en;edge[tot].va=va; ++tot;next[tot]=point[en];point[en]=tot; edge[tot].en=st;edge[tot].va=va; } int spfa(int st,int en) { int i,j,head,tail,u; memset(dis,127,sizeof(dis)); head=tail=0;que[++tail]=st; visit[st]=true;dis[st]=0; while(head!=tail) { head=head%len+1; u=que[head];visit[u]=false; for (i=point[u];i;i=next[i]) { if (dis[edge[i].en]>dis[u]+edge[i].va) { dis[edge[i].en]=dis[u]+edge[i].va; if (!visit[edge[i].en]) { visit[edge[i].en]=true; tail=tail%len+1; que[tail]=edge[i].en; } } } } return dis[en]; } int main() { int n,m,i,j,st,en,ans,k,u,v; scanf("%d%d",&n,&m); if (n==1||m==1) { ans=0x7fffffff; if (n==1) { for (i=1;i<m;++i) { scanf("%d",&j);ans=min(ans,j); } } else { for (i=1;i<n;++i) { scanf("%d",&j);ans=min(ans,j); } } if (n==1&&m==1) ans=0; } else { st=0;en=(2*(n-1)*(m-1))+1; for (i=1;i<=n;++i) for (j=1;j<m;++j) { scanf("%d",&k); u=((i-2)*(m-1)+j)*2-1; v=((i-1)*(m-1)+j)*2; if (u<0) u=0; if (v>en) v=en; add(u,v,k); } for (i=1;i<n;++i) for (j=1;j<=m;++j) { scanf("%d",&k); if (j==1) { u=en;v=((i-1)*(m-1)+j)*2-1; } else { if (j==m) { u=((i-1)*(m-1)+j-1)*2;v=0; } else { u=((i-1)*(m-1)+j)*2-1;v=u-1; } } add(u,v,k); } for (i=1;i<n;++i) for (j=1;j<m;++j) { scanf("%d",&k); u=((i-1)*(m-1)+j)*2-1;v=u+1; add(u,v,k); } ans=spfa(st,en); } printf("%d\n",ans); }