网络流建模(一)
来源:互联网 发布:行知学校 编辑:程序博客网 时间:2024/05/16 11:26
2017.8.13做的两道网络流题,难度5.5左右。
1. 1363餐巾计划问题(YZOJ)
2. 1357魔术球问题(YZOJ)
第一题
首先这道题用费用流做,是因为问题需要解决“第 i 天需要 ri 的餐巾”,且 “要使总费用最小”。
接下来考虑建模。
这个问题的难点在于,第 i 天所用的脏餐巾可供 i+m 和 i+n 天以及之后再度使用。于是设流量在通过第 i 天所代表的点之后直接与其他点连边代表再度使用。
但是第 i 天只有用脏的餐巾在可以继续使用,所以此时流量需要一个限制。那么我们可以通过拆点使 i 点本身具有容量。经过 i 点限制的流量流向 i+m 和 i+n 天之后。
图示大概这样:
其中S向A连边代表直接购买,Ai向Bi连边代表第 i 天的容量,Bi 向 B 之后连边代表提供洗好的餐巾,边的容量和费用根据意义不说了。目前为止看起来都很正常。
建模完用笔模拟一下发现不对。购买的流量会受到最后一天容量的限制。那不是可以把每一天的B向汇点连边吗?如果这样的话就无法表示继续使用了,所谓鱼和熊掌不可兼。
换个角度看,如果需要流量来同时表示对购买的餐巾计数和提供给之后的,不如设计两份流量? 第 i 天需要的流量是确定的,那提供的脏餐巾状态本来也是确定的。
那就有了如下建图:
A表示提供的脏餐巾,B表示当天需要的餐巾。
1. S往Ai连(容量为 ri,费用为 0 )的边,流量表示一天固定剩下的脏餐巾。
2. Ai 可转移到 Bi+m 之后和 Bi+n 之后,如果与之后的点都连边(inf,f或s)就是
3. S与 Bi 连边(inf,p),表示直接购买。
4. Bi 与 T 连边(ri,0),流量表示每天用过的餐巾数之和。
#include<cstdio>#include<algorithm>#include<cstring>#define R register#define inf 1<<30struct Edge{int v,c,f,nex;}edge[10000];int et,full,S,T;int st[1610],vis[1610],dis[1610],prv[1610],pre[1610];int q[1000000],l,r;void read(int &aa){ R char ch;while(ch=getchar(),ch>'9'||ch<'0');aa=ch-'0'; while(ch=getchar(),ch>='0'&&ch<='9')aa=aa*10+ch-'0';}void add(int f,int c,int a,int b){ edge[et].v=b,edge[et].c=c,edge[et].f=f,edge[et].nex=st[a],st[a]=et++; edge[et].v=a,edge[et].c=-c,edge[et].f=0,edge[et].nex=st[b],st[b]=et++;}bool spfa(){ for(R int i=1;i<=full;i++)dis[i]=inf,vis[i]=0,prv[i]=-1; dis[S]=0,vis[S]=1,q[0]=S,l=0,r=1; R int u,v,e,tmp; while(r>l) { u=q[l++],vis[u]=0; for(e=st[u];e!=-1;e=edge[e].nex)if(edge[e].f>0) { v=edge[e].v,tmp=dis[u]+edge[e].c; if(tmp<dis[v]) { dis[v]=tmp,prv[v]=u,pre[v]=e; if(!vis[v])q[r++]=v,vis[v]=1; } } } return dis[T]!=inf;}void spfa_flow(){ R int cost=0,tmp,now,e; while(spfa()) { tmp=inf; for(now=T;now!=S&&((e=pre[now])|1);now=prv[now]) if(edge[e].f<tmp)tmp=edge[e].f; for(now=T;now!=S&&((e=pre[now])|1);now=prv[now]) edge[e].f-=tmp,edge[e^1].f+=tmp; cost+=tmp*dis[T]; } printf("%d",cost);}int main(){ R int N1,P1,M1,F1,S1,i,j,r; read(full),read(P1),read(M1),read(F1),read(N1),read(S1); S=full*2+2,T=full*2+1,et=0; memset(st,-1,sizeof(st)); for(i=1;i<=full;i++) { read(r); add(r,0,S,i); add(inf,P1,S,i+full); add(r,0,i+full,T); if(i<full)add(inf,0,i,i+1); if(i+M1<=full)add(inf,F1,i,i+full+M1); if(i+N1<=full)add(inf,S1,i,i+full+N1); } full=full*2+2; spfa_flow(); return 0;}
第二题
这份题解参考了学长(n+e大神)的,讲得很不错。
这道题用暴力的话就是枚举方案数,时间复杂度是指数级的。
考虑如果确定了一个位置放的数,那么它上面可以放什么数?
可以想见,数的数量不确定,有太多方案。这种情况下,通过确定一个上界,使问题转化为判定性问题(判定性算法:“生成问题的一个解通常比验证一个给定的解时间花费要多得多。”—-百度百科)
对于一个数A,向它上面可能放的数B连一条有向边。由于B>A,这张图构成DAG。
我们发现,对于一个图求最小路径覆盖,若覆盖数<=柱子数,则该方案可行。
最小路径覆盖可以通过拆点成为二分图,通过跑最大匹配可以求出(最小路径覆盖=|V|-最大匹配)。
理论上,验证一个一个问题 期望时间 最少的是二分。但这道题跑二分的话每次都要重新建图;而从1开始枚举却满足可持久化,每次只要加入一个点,跑起来实际更快。
后面输出答案常数有点大,不够优秀。
#include<cstdio>#include<algorithm>#include<cstring>#include<cmath>#define dmin(_a,_b)(_a)<(_b)?(_a):(_b)#define R register#define inf 1<<30using namespace std;struct Edge{int to,nex,f,fo;}edge[200000];int M,N,S,T,et,st[5000],aa[5000],t1,vis[5000],to1[5000];int q[10000],l,r,level[10000];void add(int a,int b,int c){ edge[et]=(Edge){b,st[a],0,c},st[a]=et++; edge[et]=(Edge){a,st[b],0,0},st[b]=et++;}bool bfs(){ memset(level,0,sizeof(level)); l=0,r=1,q[0]=S,level[S]=1; R int u,v,e; while(l<r) { u=q[l++]; for(e=st[u];e!=-1;e=edge[e].nex) if(!level[edge[e].to]&&edge[e].f>0) { v=edge[e].to; level[v]=level[u]+1; q[r++]=v; if(v==T)return true; } } return false;}int dfs(int u,const int flow){ if(u==T)return flow; R int v,e,f,ret=0,tmp; for(e=st[u];e!=-1;e=edge[e].nex) if(level[edge[e].to]==level[u]+1&&edge[e].f>0) { v=edge[e].to; tmp=dmin(flow-ret,edge[e].f); f=dfs(v,tmp); edge[e].f-=f,edge[e^1].f+=f,ret+=f; if(ret==flow)return ret; } return ret;}bool dinic(R int tmp){ for(R int i=0;i<et;i++)edge[i].f=edge[i].fo; while(bfs())tmp-=dfs(S,inf); return tmp<=N;}void rec(R int u,R int pre){ if(u==T){aa[t1++]=-1;return;} aa[t1++]=u>>1; R int v,e; for(e=st[u];e!=-1;e=edge[e].nex) if(edge[e].fo==1&&edge[e].f==0&&edge[e].to!=pre) { v=edge[e].to; rec(v,u); }}int main(){ R int i,j,k,a,b; scanf("%d",&N); memset(st,-1,sizeof(st)); S=0,T=1,et=0; for(i=1;1;++i) { add(S,i<<1,1);add(i<<1|1,T,1); b=(int)sqrt(i+i-1),a=(int)sqrt(i)+1; for(j=a;j<=b;j++) if(j*j-i<i)add((j*j-i)<<1,i<<1|1,1); if(!dinic(i))break; t1=1,rec(S,S); } M=i-1;printf("%d\n",M); j=aa[2]; for(i=3;i<t1;i++) { if(aa[i]==-1){j=aa[i+1];continue;} to1[j]=aa[i],j=aa[i]; } for(i=1;i<=M;++i)if(vis[i]==0) { j=i; while(j!=0)printf("%d ",j),vis[j]=1,j=to1[j]; printf("\n"); } return 0;}
- 网络流建模(一)
- 网络流建模(二)
- 网络流建模方法(Wait)
- POJ 3281 Dining(网络流建模)
- 网络流(多样的建模)
- 网络流建模总结
- 网络流建模学习
- 网络流建模总结
- 网络流建模(三)
- 建模鸡汤(一)
- UML建模(一)
- [NetworkFlow]网络流建模相关
- 网络流建模学习笔记
- 网络流建模汇总结
- 11082 - Matrix Decompressing (网络流建模|二分图匹配)
- poj1149(*网络流建模方法,paper题)
- 网络流建模汇总(转自Edelweiss)
- UML-结构建模(一)
- 哎呦不错哦!
- 国际电子邮件协会判定垃圾邮件规则
- python测试函数
- 例3-2 用振幅为0.8的方波进行傅里叶分析,并用傅里叶分析得到的系数求解当K为不同值时的合成图。
- 389. Find the Difference
- 网络流建模(一)
- 74. Search a 2D Matrix
- 学习图片格式
- Bochs上运行BIOS启动代码
- Python3爬虫下载pdf(二)
- 安卓四大组件(二)
- 网络中进程间的通信----Socket
- php之socket入门教程
- 让C#轻松实现读写锁分离--封装ReaderWriterLockSlim