网络流24题刷题记录
题目一:飞行员配对方案问题
一群飞行员和另一群飞行员之间有的可以配对,有的不能配对,求最多可以配多少对?
典型二分图最大匹配问题。可以用匈牙利算法 或者 加上源点汇点之后跑最大流。【感觉第二个打错的概率还低一些】。
【还是介绍一下匈牙利算法吧】【看白书大法好!】
从左边(s集)一个未盖点出发(还有没有和任何人匹配的点)出发,顺次经过未选边->选边->未选边.....【这样的路叫做交替路】
如果路径当中经过一个未盖点【这样的交替路叫增广路】...那么将所有的选边变成不选,不选的边选上,就可以多一条匹配的边了。(画个图就很好理解啦)
找到增广路后变化一次就会多一条匹配边。最大匹配就是图中找不到增广路的时候。
下面给出的是BFS事先的匈牙利算法。【想彻底踏实地了解这个算法还是老老实实看白书去】
1 /* 2 Author : Robert Yuan 3 */ 4 #include<cstdio> 5 #include<cstring> 6 #include<cstdlib> 7 #include<algorithm> 8 9 using namespace std; 10 11 const int maxn=1010; 12 13 struct Node{ 14 int data,next; 15 }node[maxn*maxn]; 16 17 #define now node[point].data 18 #define then node[point].next 19 20 int n,m,cnt; 21 int head[maxn]; 22 23 inline int in(){ 24 int x=0,flag=1;char ch=getchar(); 25 while((ch>'9' || ch<'0') && ch!='-') ch=getchar(); 26 if(ch=='-') flag=-1,ch=getchar(); 27 while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar(); 28 return x*flag; 29 } 30 31 inline void add(int u,int v){ 32 node[++cnt].data=v;node[cnt].next=head[u];head[u]=cnt; 33 node[++cnt].data=u;node[cnt].next=head[v];head[v]=cnt; 34 } 35 36 void prework(){ 37 n=in();m=in(); 38 for(int i=1;i<=n+m;i++) head[i]=-1; 39 int u,v; 40 while(1){ 41 u=in();v=in(); 42 if(u==-1) break; 43 add(u,v); 44 } 45 } 46 47 int a[maxn*maxn],pre[maxn],check[maxn],matching[maxn]; 48 49 int Match(){ //二分图最大匹配,BFS方法 50 memset(matching,-1,sizeof(matching)); 51 memset(check,-1,sizeof(check)); 52 int H,T,point,top,ans=0; 53 for(int i=1;i<=n;i++) 54 if(matching[i]==-1){ //在左边的点中选出一个未盖点 55 H=0;T=1;a[1]=i;pre[i]=-1; //pre[i]记录下 i点由谁(一个左边的点)拓展而来 56 bool find=false; //还没有找到增广路 57 while(H<T && !find){ //从选出的未盖点开始拓展找增广路 58 H++; 59 point=head[a[H]]; 60 while(point!=-1 && !find){ //枚举连接的每一条边 (连接的肯定是右边的某点)而且这条边一定是未匹配边 61 if(check[now]!=i){ //标记这个点在 i拓展时已经被走过,那就不需要再走一次了 62 check[now]=i; 63 a[++T]=matching[now]; //右边到左边一定是走走匹配边,又从右边回到左边 ,所以说每次其实是走两条边 64 if(matching[now]>0) //说明这个点不是未盖点 (如果找到增广路,那么最后一个未盖点也一定在右边) 65 pre[matching[now]]=a[H]; 66 else{ 67 find=true; 68 int d=a[H],e=now; //因为每次只拓展一层,这里的 d是与最后一个未盖点连接的左边的点 69 while(d!=-1){ //将走过的路上的匹配边与未匹配边交换 70 int t=matching[d]; //记录d的匹配(右边的点) 71 matching[d]=e; //将d,e匹配 72 matching[e]=d; 73 d=pre[d]; //d变成之前扩展来的点(左边的点) ,e变成d的匹配边连接的点 (右边的点) 74 e=t; 75 } 76 } 77 } 78 point=then; 79 } 80 } 81 if(matching[i]!=-1) ans++; 82 } 83 return ans; 84 } 85 86 void mainwork(){ 87 int flag=Match(); 88 if(!flag) 89 printf("No Solution!"); 90 else{ 91 printf("%d\n",flag); 92 for(int i=1;i<=n;i++) 93 if(matching[i]>0) 94 printf("%d %d\n",i,matching[i]); 95 } 96 } 97 98 int main(){ 99 #ifndef ONLING_JUDGE 100 freopen("air.in","r",stdin); 101 freopen("air.out","w",stdout); 102 #endif 103 prework(); 104 mainwork(); 105 return 0; 106 }
第二题:太空飞行计划问题
每个实验都有赚取的利润,每个实验同时又有着仪器的需要,仪器当然是要钱的。求做哪些实验可以让科学家赚最多的钱。
我们发现不能简单的用实验的收益减去它需要的仪器的费用——因为一个仪器可能被多个实验使用。
于是可以想到最大流。仪器的费用由所有用它的实验共同承担(因为用仪器的费用连边限制了这个仪器只会买一次,而只需要在仪器和实验之间连边就可以让所有实验共同来承担)。
【具体建图】
把每个实验看作二分图X集合中的顶点,每个设备看作二分图Y集合中的顶点,增加源S和汇T。
1、从S向每个Xi连接一条容量为该点收入的有向边。
2、从Yi向T连接一条容量为该点支出的有向边。
3、如果一个实验i需要设备j,连接一条从Xi到Yj容量为无穷大的有向边。
这样连完边之后最后的答案就是所有的实验的费用-得到的最大流。
【简单的分析】
首先可以简单的发现,我们将源点与实验连边,边权为实验赚的钱,就相当于我们用这些实验赚的钱来买装备。首先想一想,如果与实验相连的所有仪器都是满流的(其实是所有与这些仪器有关的实验共同努力的结果),说明这个实验可以赚钱,这些仪器可以购买。如果与实验相连的所有仪器只要有一个不满的,说明我这个实验的钱全部用完了,还有其它要用这个仪器的一起努力,也买不起这个仪器,这个仪器就不会买了,如果这种情况,这个实验的钱会全部砸入图中(流进最大流中)。所以如果可以赚钱,这个实验流入到图中的流就是它需要对它购买的仪器所出的力,如果这个仪器不赚钱,那么它流入图中的流就是它所有的钱。于是乎,Ans=所有的实验的收益-得到的最大流。
最后,怎么算出要做哪些实验呢?如果这个实验是亏的,那么汇点到它就是满流的,所以直接在残余网络上走,能走到的实验就是要做的(说明还有剩余流)。
【可是数据中出现了有的实验可做可不做的情况(就是收益与付出相同的时候),而且数据中有的做了,有的没有做——表示无法AC】
/* Problem : Author : Robert Yuan Memory : Time : */ #include <cstdio> #include <cstring> #include <cstdlib> #include <algorithm> using namespace std; char ch; inline int in(){ int x=0; while(ch>'9' || ch<'0'){if(ch=='\n') {ch=getchar();return -1;}ch=getchar();} while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar(); return x; } const int maxn=1010; const int INF=0x7fffffff; struct Node{ int data,next,weight; }node[maxn*maxn]; #define now node[point].data #define then node[point].next #define www node[point].weight int n,m,s=0,t,ans,cnt; int head[maxn]; int dis[maxn],a[maxn]; void add(int u,int v,int w){ node[++cnt].data=v;node[cnt].weight=w;node[cnt].next=head[u];head[u]=cnt; node[++cnt].data=u;node[cnt].weight=0;node[cnt].next=head[v];head[v]=cnt; } void prework(){ m=in();n=in();ch=getchar(); for(int i=0;i<=n+m+1;i++) head[i]=-1; int x;t=n+m+1; for(int i=1;i<=m;i++){ x=in(); add(0,i,x);ans+=x; while(1){ x=in(); if(x<0) break; x+=m; add(i,x,INF); } } for(int i=1;i<=n;i++) x=in(),add(i+m,t,x); } bool BFS(){ memset(dis,-1,sizeof(dis)); int H=0,T=1,point; dis[0]=0;a[1]=0; while(H<T){ H++; point=head[a[H]]; while(point!=-1){ if(www>0 && dis[now]<0) a[++T]=now,dis[now]=dis[a[H]]+1; point=then; } } if(dis[t]<0) return false; return true; } int dfs(int x,int low){ if(x==t) return low; int Low,point=head[x]; while(point!=-1){ if(www>0 && dis[now]==dis[x]+1){ Low=dfs(now,min(low,www)); if(Low){ node[point].weight-=Low; if(point&1) node[point+1].weight+=Low; else node[point-1].weight+=Low; return Low; } } point=then; } return 0; } bool judge(int x){ for(int point=head[x];point!=-1;point=then) if(now==t && www!=0) return false; return true; } bool special_dfs(int x){ for(int point=head[x];point!=-1;point=then) if(!judge(now)) return false; return true; } void mainwork(){ int flag; while(BFS()){ while(1){ flag=dfs(0,INF); if(!flag) break; ans-=flag; } } memset(dis,-1,sizeof(dis)); int H=0,T=1,point; dis[s]=0;a[1]=0; while(H<T){ H++; point=head[a[H]]; while(point!=-1){ if(www>0 && dis[now]<0) a[++T]=now,dis[now]=dis[a[H]]+1; point=then; } } /*point=head[0]; while(point!=-1){ if(www==0) if(special_dfs(now)){ dis[now]=1; int point2=head[now]; while(point2!=-1){ dis[node[point2].data]=1; point2=node[point2].next; } }; point=then; }*/ for(int i=1;i<=m;i++) if(dis[i]>0) printf("%d ",i); putchar('\n'); for(int j=m+1;j<=m+n;j++) if(dis[j]>0) printf("%d ",j-m); putchar('\n'); printf("%d",ans); } int main(){ freopen("shut.in","r",stdin); freopen("shut.out","w",stdout); prework(); mainwork(); return 0; }
第三题:最小路径覆盖问题
给你一张有向无环图,让你选出最少的路径,覆盖所有的点。
【建模方法】
构造二分图,把原图每个顶点i拆分成二分图X,Y集合中的两个顶点Xi和Yi。对于原图中存在的每条边(i,j),在二分图中连接边(Xi,Yj)。
然后把二分图最大匹配模型转化为网络流模型,求网络最大流。【或者同第一题使用匈牙利算法】
最小路径覆盖的条数,就是原图顶点数,减去二分图最大匹配数。沿着匹配边查找,就是一个路径上的点,输出所有路径即可。
【简易证明:最小路径覆盖=原图顶点数-二分图最大匹配数——转自Wall-F】
根据定义最小路径覆盖里要求同一个点只可以属于一条路径,即路径时不可以开叉的,如果在二分图里选两条有公共点的边那么反应在原图上就是路径有岔路,那么就不符合匹配的定义了,所以二分图里选的边必须是无公共交点的,这转化到最大匹配了。
1 /* 2 Problem : 3 Author : Robert Yuan 4 Memory : 5 Time : 6 */ 7 8 #include <cstdio> 9 #include <cstring> 10 #include <cstdlib> 11 #include <algorithm> 12 13 using namespace std; 14 15 const int maxn=420; 16 17 int n,m,cnt,ans; 18 int matching[maxn],check[maxn],head[maxn]; 19 20 struct Node{ 21 int data,next; 22 }node[maxn*maxn]; 23 24 #define now node[point].data 25 #define then node[point].next 26 27 inline int in(){ 28 int x=0;char ch=getchar(); 29 while(ch>'9' || ch<'0') ch=getchar(); 30 while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar(); 31 return x; 32 } 33 34 void add(int u,int v){ 35 node[++cnt].data=v;node[cnt].next=head[u];head[u]=cnt; 36 //node[++cnt].data=u;node[cnt].next=head[v];head[v]=cnt; 37 } 38 39 void prework(){ 40 int u,v; 41 n=in();m=in();ans=n; 42 for(int i=1;i<=(n<<1);i++) head[i]=-1; 43 for(int i=1;i<=m;i++){ 44 u=in();v=in(); 45 add(u,v+n); 46 add(u+n,v); 47 } 48 } 49 50 int a[maxn],pre[maxn]; 51 bool vis[maxn]; 52 53 void mainwork(){ 54 memset(matching,-1,sizeof(matching)); 55 memset(check,-1,sizeof(check)); 56 int H,T,point; 57 bool find; 58 for(int i=1;i<=n;i++) 59 if(matching[i]==-1){ 60 find=false;H=0,T=1;a[1]=i;pre[i]=-1; 61 while(H<T && !find){ 62 H++; 63 point=head[a[H]]; 64 while(point!=-1 && !find){ 65 if(check[now]!=i){ 66 check[now]=i; 67 a[++T]=matching[now]; 68 if(matching[now]>0) 69 pre[matching[now]]=a[H]; 70 else{ 71 find=true; 72 int d=a[H],e=now; 73 while(d!=-1){ 74 int t=matching[d]; 75 matching[d]=e; 76 matching[e]=d; 77 d=pre[d]; 78 e=t; 79 } 80 } 81 } 82 point=then; 83 } 84 } 85 if(matching[i]!=-1) ans--; 86 } 87 for(int i=1;i<=n;i++) 88 if(!vis[i]){ 89 int d=i; 90 while(d>0){ 91 printf("%d ",d); 92 vis[d]=true; 93 d=matching[d]-n; 94 } 95 putchar('\n'); 96 } 97 printf("%d",ans); 98 } 99 100 int main(){ 101 freopen("path.in","r",stdin); 102 freopen("path.out","w",stdout); 103 prework(); 104 mainwork(); 105 return 0; 106 }
第四题:魔术球问题
有n根柱子,在柱子上放m个球(带有编号),每次只能放最上面,并且要求同一根柱子上的两个相邻球的编号之和为完全平方数。求出m的最大值。并输出方案
【建模分析】对于i,j两个球(i<j)若i+j为完全平方数,则连一条边,那么对于m个球,最小路径覆盖就相当于n(是否觉得很形象?一根柱子就是一条路径)。即n根柱子能放得下这m个球。所以只需从小到大枚举m(不断也就是加入新节点)直到最小路径覆盖>n了,那么当前m-1即为所求。【使用枚举的原因:只需在残余网络中加入节点,接着跑最大流就可以了】
【悄悄告诉你】:听说贪心的出来的最大值和网络流跑出来一模一样【贪心就是对于新加进的节点m,在n个中找到一个可以放的,就放下,不然就可以返回m-1了】。还听说有一个关于最大值的公式:ans=(n*n-1)/2+n
1 /* 2 * Problem: 线性规划与网络流24题 #4 魔术球问题 3 * Author: Guo Jiabao 4 * Time: 2009.6.27 12:06 5 * State: Solved 6 * Memo: 网络最大流 最小路径覆盖 枚举答案 7 */ 8 #include <iostream> 9 #include <cstdio> 10 #include <cstdlib> 11 #include <cmath> 12 #include <cstring> 13 using namespace std; 14 const int MAXN=4001,OFFSET=2000,MAXM=200000,INF=~0U>>1; 15 struct edge 16 { 17 edge *next,*op; 18 int t,c; 19 }*V[MAXN],*P[MAXN],ES[MAXM],*Stae[MAXN]; 20 int N,M,S,T,EC,Ans,Maxflow; 21 int Lv[MAXN],Stap[MAXN],Match[MAXN],Path[MAXN]; 22 bool vis[MAXN],issquare[MAXN]; 23 inline void addedge(int a,int b,int c) 24 { 25 ES[++EC].next = V[a]; V[a]=ES+EC; V[a]->t=b; V[a]->c=c; 26 ES[++EC].next = V[b]; V[b]=ES+EC; V[b]->t=a; V[b]->c=0; 27 V[a]->op = V[b]; V[b]->op = V[a]; 28 } 29 void init() 30 { 31 freopen("ball.in","r",stdin); 32 freopen("ball.out","w",stdout); 33 scanf("%d",&N); 34 S=0; T=MAXN-1; 35 for (int i=1;i<=60;i++) 36 issquare[i*i]=true; 37 } 38 bool Dinic_Label() 39 { 40 int head,tail,i,j; 41 Stap[head=tail=0]=S; 42 memset(Lv,-1,sizeof(Lv)); 43 Lv[S]=0; 44 while (head<=tail) 45 { 46 i=Stap[head++]; 47 for (edge *e=V[i];e;e=e->next) 48 { 49 j=e->t; 50 if (e->c && Lv[j]==-1) 51 { 52 Lv[j] = Lv[i]+1; 53 if (j==T) 54 return true; 55 Stap[++tail] = j; 56 } 57 } 58 } 59 return false; 60 } 61 void Dinic_Augment() 62 { 63 int i,j,delta,Stop; 64 for (i=S;i<=T;i++) 65 P[i] = V[i]; 66 Stap[Stop=1]=S; 67 while (Stop) 68 { 69 i=Stap[Stop]; 70 if (i!=T) 71 { 72 for (;P[i];P[i]=P[i]->next) 73 if (P[i]->c && Lv[i] + 1 == Lv[j=P[i]->t]) 74 break; 75 if (P[i]) 76 { 77 Stap[++Stop] = j; 78 Stae[Stop] = P[i]; 79 } 80 else 81 Stop--,Lv[i]=-1; 82 } 83 else 84 { 85 delta = INF; 86 for (i=Stop;i>=2;i--) 87 if (Stae[i]->c < delta) 88 delta = Stae[i]->c; 89 Maxflow += delta; 90 for (i=Stop;i>=2;i--) 91 { 92 Stae[i]->c -= delta; 93 Stae[i]->op->c += delta; 94 if (Stae[i]->c==0) 95 Stop = i-1; 96 } 97 } 98 } 99 } 100 void Dinic() 101 { 102 while (Dinic_Label()) 103 Dinic_Augment(); 104 } 105 void solve() 106 { 107 int i,j; 108 addedge(S,1,1); 109 addedge(1+OFFSET,T,1); 110 Dinic(); 111 for (i=1;i - Maxflow <= N;) 112 { 113 i++; 114 addedge(S,i,1); 115 addedge(i+OFFSET,T,1); 116 for (j=1;j<i;j++) 117 if (issquare[i+j]) 118 addedge(j,i+OFFSET,1); 119 Dinic(); 120 } 121 Ans = i-1; 122 memset(V,0,sizeof(V)); 123 EC=Maxflow=0; 124 for (i=1;i<=Ans;i++) 125 { 126 addedge(S,i,1); 127 addedge(i+OFFSET,T,1); 128 for (j=1;j<i;j++) 129 if (issquare[i+j]) 130 addedge(j,i+OFFSET,1); 131 } 132 Dinic(); 133 } 134 void print() 135 { 136 int i,j,p; 137 printf("%d\n",Ans); 138 for (i=Ans+OFFSET;i>=1+OFFSET;i--) 139 { 140 for (edge *e=V[i];e;e=e->next) 141 { 142 if (e->c==1) 143 { 144 Match[e->t] = i-OFFSET; 145 break; 146 } 147 } 148 } 149 for (i=1;i<=Ans;i++) 150 { 151 if (vis[i]) continue; 152 p=0; 153 for (j=i;j;j=Match[j]) 154 { 155 Path[++p]=j; 156 vis[j]=true; 157 } 158 for (j=1;j<p;j++) 159 printf("%d ",Path[j]); 160 printf("%d\n",Path[j]); 161 } 162 } 163 int main() 164 { 165 init(); 166 solve(); 167 print(); 168 return 0; 169 }
1 /* 2 Problem : 听说可以贪心+听说有一个公式 3 Author : Robert Yuan 4 Memory : 5 Time : 6 */ 7 #include <cmath> 8 #include <cstdio> 9 #include <cstring> 10 #include <cstdlib> 11 #include <algorithm> 12 13 using namespace std; 14 15 int ans,n; 16 int a[1010][1010]; 17 18 void prework(){ 19 scanf("%d",&n); 20 } 21 22 bool pd(int t){ 23 int x=sqrt(t); 24 if(x*x==t || (x+1)*(x+1)==t) return true; 25 return false; 26 } 27 28 void mainwork(){ 29 ans=((n*n-1)>>1)+n; 30 for(int i=1;i<=ans;i++){ 31 for(int T=1;T<=n;T++) 32 if(!a[T][0] || pd(a[T][a[T][0]]+i)){ 33 a[T][0]++;a[T][a[T][0]]=i;break; 34 } 35 } 36 printf("%d\n",ans); 37 for(int i=1;i<=n;i++){ 38 for(int j=1;j<=a[i][0];j++) 39 printf("%d ",a[i][j]); 40 putchar('\n'); 41 } 42 } 43 44 int main(){ 45 #ifndef ONLINE_JUDGE 46 freopen("ball.in","r",stdin); 47 freopen("ball.out","w",stdout); 48 #endif 49 prework(); 50 mainwork(); 51 return 0; 52 }
第五题:圆桌问题
有n堆人,每堆分别有r1,r2,r3...rn个人。还有m张桌子,每张桌子分别能坐a1,a2,a3...am个人。要求每堆中的人都不坐在一张桌子上。求方案是否存在并求出这个方案。
【建模分析】首先很容易想到的是,将人比作流来建图。加入S,T,从源点向每堆人连一条容量为堆得人数的边,从每个桌子向汇点连一条容量为桌子容量的边。因为一堆人只能分开做,所以在每个桌子和每个人堆之间连一条容量大小为1的边。然后求最大流即可。
无解即最大流小于总人数,输出方案的话,只需判断剩余网络中每个人堆连着桌子的边是否满流即可。
1 /* 2 Problem : 3 Author : Robert Yuan 4 Memory : 5 Time : 6 */ 7 8 #include <cstdio> 9 #include <cstring> 10 #include <cstdlib> 11 #include <algorithm> 12 13 using namespace std; 14 15 inline int in(){ 16 int x=0;char ch=getchar(); 17 while(ch>'9' || ch<'0') ch=getchar(); 18 while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar(); 19 return x; 20 } 21 22 int n,m,cnt,s=0,t,sum,ans; 23 const int maxn=510; 24 const int INF=0x7ffff; 25 int head[maxn],dis[maxn],a[maxn],vis[maxn]; 26 27 struct Node{ 28 int data,next,weight; 29 }node[maxn*maxn]; 30 31 #define now node[point].data 32 #define then node[point].next 33 #define www node[point].weight 34 35 void add(int u,int v,int w){ 36 node[++cnt].data=v;node[cnt].next=head[u];node[cnt].weight=w;head[u]=cnt; 37 node[++cnt].data=u;node[cnt].next=head[v];node[cnt].weight=0;head[v]=cnt; 38 } 39 40 void prework(){ 41 n=in();m=in();t=n+m+1; 42 for(int i=0;i<=t;i++) head[i]=-1; 43 int x; 44 for(int i=1;i<=n;i++) 45 x=in(),add(0,i,x),sum+=x; 46 for(int j=1;j<=m;j++) 47 x=in(),add(j+n,t,x); 48 for(int i=1;i<=n;i++) 49 for(int j=1;j<=m;j++) 50 add(i,j+n,1); 51 } 52 53 bool BFS(){ 54 memset(dis,-1,sizeof(dis)); 55 dis[0]=1;a[1]=0; 56 int H=0,T=1,point; 57 while(H<T){ 58 H++; 59 point=head[a[H]]; 60 while(point!=-1){ 61 if(www>0 && dis[now]<0) 62 a[++T]=now,dis[now]=dis[a[H]]+1; 63 point=then; 64 } 65 } 66 if(dis[t]>0) return true; 67 return false; 68 } 69 70 int dfs(int x,int low){ 71 if(x==t) return low; 72 int point=head[x],Low; 73 while(point!=-1){ 74 if(www>0 && dis[now]==dis[x]+1){ 75 Low=dfs(now,min(low,www)); 76 if(Low){ 77 node[point].weight-=Low; 78 if(point&1) node[point+1].weight+=Low; 79 else node[point-1].weight+=Low; 80 return Low; 81 } 82 } 83 point=then; 84 } 85 return 0; 86 } 87 88 void mainwork(){ 89 int flag; 90 while(BFS()){ 91 while(1){ 92 flag=dfs(0,INF); 93 if(!flag) break; 94 ans+=flag; 95 } 96 } 97 if(ans!=sum){ 98 printf("0");return; 99 } 100 printf("1\n"); 101 for(int i=1;i<=n;i++){ 102 memset(vis,0,sizeof(vis)); 103 int point=head[i]; 104 while(point!=-1){ 105 if(www==0) 106 vis[now-n]=true; 107 point=then; 108 } 109 for(int i=1;i<=m;i++) 110 if(vis[i]) 111 printf("%d ",i); 112 putchar('\n'); 113 } 114 115 } 116 117 int main(){ 118 #ifndef ONLINE_JUDGE 119 freopen("table.in","r",stdin); 120 freopen("table.out","w",stdout); 121 #endif 122 prework(); 123 mainwork(); 124 return 0; 125 } 126
第六题:最长递增子序列问题
给你一个序列。1>求最长递增子序列的长度k。2>若每个点只能被选取一次,求同时选取出长度为k的递增子序列,最多能一次选出多少。3>如果第一个点和最后一个点可以选取多次,求同时选取出长度为k的递增子序列,最多能一次选出多少。(注意:虽然题目说是严格上升,但标程&数据实际是不严格上升的)
【问题划分】第一问:动态规划经典题。二三问:最大流算法。
【动态规划】关于最长不降子序列:使用优化,维护一个不降的序列temp[],temp[i]表示现有的 末尾数字最大的 长度为i的 最长不降子序列的 末尾数字为temp[i]。刚开始先默认选择数组的第一个元素放在temp[1]的位置,接着从原数组中依次选出数来。如果比维护的序列的最后一位temp[top]还大,则temp[++top]=num[i],扩充维护的不降序列,将当前的数字添加到数组的尾部。如果不是这样,就在维护的不降序列中选出一个位置j,使得 temp[j-1]<=num[i]<temp[j],然后就可以将temp[j]替换为num[i],因为同样的长度,如果末尾的数字最小,则更容易构造出一个更优的不降序列。最后的答案便是top。选出位置的这个步骤使用二分查找,这样算法的复杂度就成O(nlogn)。【然而在这道题中并没有什么用...我依旧使用的是O(n^2)的...】
【建模分析】(F[i]表示使用第i位,从i位出发到序列末尾能构成的最长不降子序列长度,A[i]表示第i位的数值大小)
1、把序列每位i拆成两个点<i.a>和<i.b>,从<i.a>到<i.b>连接一条容量为1的有向边。【这是为了保证每个元素只会被取一次,当这条连接两个相同意义的节点的边满流的时候,说明这个点被应用了】
2、建立附加源S和汇T,如果序列第i位有F[i]=K,从S到<i.a>连接一条容量为1的有向边。【这一步和第4步一起,会构造出所有能构造出一个长度为k的递增子序列,而与汇点相连的点都是这些序列的起点】
3、如果F[i]=1,从<i.b>到T连接一条容量为1的有向边。 【这些点一定是序列的结尾】
4、如果j>i且A[i] < A[j]且F[j]+1=F[i],从<i.b>到<j.a>连接一条容量为1的有向边。
求网络最大流,就是第二问的结果。把边(<1.a>,<1.b>)(<N.a>,<N.b>)(S,<1.a>)(<N.b>,T)这四条边的容量修改为无穷大(即添加一条正无穷的边),再求一次网络最大流,就是第三问结果。【这样就相当于这些点可以随意取几次(让任意多个子序列穿过它们)】
【注意】”边(<1.a>,<1.b>)(<N.a>,<N.b>)(S,<1.a>)(<N.b>,T)这四条边的容量修改为无穷大“这句话表示:必须是原本存在的边才能给它们增流,不然可能导致不好的事情发生(例如1原本不是长度为k序列的开始,这样增流之后,就会产生不是长度为k的序列了)
1 /* 2 Problem : 3 Author : Robert Yuan 4 Memory : 5 Time : 6 */ 7 8 #include <cmath> 9 #include <cstdio> 10 #include <cstring> 11 #include <cstdlib> 12 #include <algorithm> 13 14 using namespace std; 15 16 inline int in(){ 17 int x=0;char ch=getchar(); 18 while(ch>'9' || ch<'0') ch=getchar(); 19 while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar(); 20 return x; 21 } 22 23 const int maxn=1010; 24 const int INF=0x7fffffff; 25 26 struct Node{ 27 int data,next,weight; 28 }node[maxn*maxn]; 29 30 #define now node[point].data 31 #define then node[point].next 32 #define www node[point].weight 33 34 int n,k,t,ans,cnt; 35 int a[maxn],f[maxn],head[maxn],cur[maxn]; 36 int dis[maxn],que[maxn]; 37 38 void add(int u,int v,int w){ 39 node[cnt].data=v;node[cnt].next=head[u];node[cnt].weight=w;head[u]=cnt++; 40 node[cnt].data=u;node[cnt].next=head[v];node[cnt].weight=0;head[v]=cnt++; 41 } 42 43 void prework(){ 44 n=in();t=(n<<1)+1; 45 for(int i=0;i<=t;i++) head[i]=-1; 46 for(int i=1;i<=n;i++) 47 a[i]=in(),f[i]=1; 48 for(int i=n;i>=1;i--) 49 for(int j=i+1;j<=n;j++) 50 if(a[i]<=a[j] && f[i]<f[j]+1) 51 f[i]=f[j]+1; 52 for(int i=1;i<=n;i++) 53 if(f[i]>k) k=f[i]; 54 printf("%d\n",k); 55 for(int i=1;i<=n;i++){ 56 //add(i,i+n,1); 57 if(f[i]==k) add(0,i,1); 58 if(f[i]==1) add(i,t,1); 59 for(int j=i+1;j<=n;j++) 60 if(a[j]>=a[i] && f[i]==f[j]+1) //这里是>= 61 add(i,j,1); 62 } 63 } 64 65 bool BFS(){ 66 memset(dis,-1,sizeof(dis)); 67 int H=0,T=1,point; 68 que[1]=0;dis[0]=0; 69 while(H<T){ 70 H++; 71 point=head[que[H]]; 72 while(point!=-1){ 73 if(www>0 && dis[now]<0) 74 que[++T]=now,dis[now]=dis[que[H]]+1; 75 point=then; 76 } 77 } 78 if(dis[t]>0) return true; 79 return false; 80 } 81 82 int dfs(int x,int low){ 83 if(x==t) return low; 84 int Low,point=cur[x]; 85 while(point!=-1){ 86 if(www>0 && dis[now]==dis[x]+1){ 87 Low=dfs(now,min(low,www)); 88 if(Low){ 89 www-=Low; 90 node[point^1].weight+=Low; 91 cur[x]=point; 92 return Low; 93 } 94 } 95 point=then; 96 } 97 return 0; 98 } 99 100 void mainwork(){ 101 int flag; 102 while(BFS()){ 103 for(int i=1;i<=n;i++) cur[i]=head[i]; 104 while(1){ 105 flag=dfs(0,INF); 106 if(!flag) break; 107 ans+=flag; 108 } 109 } 110 printf("%d\n",ans); 111 if(f[1]==k) add(0,1,INF); //注意这里要有流才增流 112 add(n,t,INF); 113 while(BFS()){ 114 while(1){ 115 flag=dfs(0,INF); 116 if(!flag) break; 117 ans+=flag; 118 } 119 } 120 printf("%d",ans); 121 } 122 123 int main(){ 124 #ifndef ONLINE_JUDGE 125 freopen("alis.in","r",stdin); 126 freopen("alis.out","w",stdout); 127 #endif 128 prework(); 129 mainwork(); 130 return 0; 131 }
试题七:试题库问题
有n个题目,每个题目都有它所属于的类别,共有m个类别。现要出一套试卷使得m个类别分别有r1,r2,r3....rm个题目,问你该如何选择。
【建模分析】
建立二分图,每个类别为X集合中的顶点,每个题为Y集合中的顶点,增设附加源S和汇T。
1、从S向每个Xi连接一条容量为该类别所需数量的有向边。
2、从每个Yi向T连接一条容量为1的有向边。
3、如果一个题i属于一个类别j,连接一条从Xj到Yi容量为1的有向边。
求网络最大流,如果最大流量等于所有类别所需之和,则存在解,否则无解。对于每个类别,从X集合对应点出发的所有满流边,指向的B集合中的顶点就是该类别的所选的题(一个可行解)。
1 /* 2 Problem : 3 Author : Robert Yuan 4 Memory : 5 Time : 6 */ 7 8 #include <cstdio> 9 #include <cstring> 10 #include <cstdlib> 11 #include <algorithm> 12 13 using namespace std; 14 15 inline int in(){ 16 int x=0;char ch=getchar(); 17 while(ch>'9' || ch<'0') ch=getchar(); 18 while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar(); 19 return x; 20 } 21 22 int n,m,cnt,s=0,t,sum,ans; 23 const int maxn=1010; 24 const int INF=0x7ffff; 25 int head[maxn],dis[maxn],a[maxn]; 26 bool vis[maxn]; 27 28 struct Node{ 29 int data,next,weight; 30 }node[maxn*maxn]; 31 32 #define now node[point].data 33 #define then node[point].next 34 #define www node[point].weight 35 36 void add(int u,int v,int w){ 37 node[++cnt].data=v;node[cnt].next=head[u];node[cnt].weight=w;head[u]=cnt; 38 node[++cnt].data=u;node[cnt].next=head[v];node[cnt].weight=0;head[v]=cnt; 39 } 40 41 void prework(){ 42 n=in();m=in();t=n+m+1; 43 for(int i=0;i<=t;i++) head[i]=-1; 44 int x,u; 45 for(int i=1;i<=n;i++) 46 x=in(),add(i,t,x),sum+=x; 47 for(int j=1;j<=m;j++){ 48 x=in();add(0,j+n,1); 49 while(x--){ 50 u=in(); 51 add(j+n,u,1); 52 } 53 } 54 } 55 56 bool BFS(){ 57 memset(dis,-1,sizeof(dis)); 58 dis[0]=1;a[1]=0; 59 int H=0,T=1,point; 60 while(H<T){ 61 H++; 62 point=head[a[H]]; 63 while(point!=-1){ 64 if(www>0 && dis[now]<0) 65 a[++T]=now,dis[now]=dis[a[H]]+1; 66 point=then; 67 } 68 } 69 if(dis[t]>0) return true; 70 return false; 71 } 72 73 int dfs(int x,int low){ 74 if(x==t) return low; 75 int point=head[x],Low; 76 while(point!=-1){ 77 if(www>0 && dis[now]==dis[x]+1){ 78 Low=dfs(now,min(low,www)); 79 if(Low){ 80 node[point].weight-=Low; 81 if(point&1) node[point+1].weight+=Low; 82 else node[point-1].weight+=Low; 83 return Low; 84 } 85 } 86 point=then; 87 } 88 return 0; 89 } 90 91 void mainwork(){ 92 int flag; 93 while(BFS()){ 94 while(1){ 95 flag=dfs(0,INF); 96 if(!flag) break; 97 ans+=flag; 98 } 99 } 100 if(ans!=sum){ 101 printf("No Solution!");return; 102 } 103 for(int i=1;i<=n;i++){ 104 printf("%d: ",i); 105 memset(vis,0,sizeof(vis)); 106 int point=head[i]; 107 while(point!=-1){ 108 if(www>0) 109 vis[now-n]=true; 110 point=then; 111 } 112 for(int i=1;i<=m;i++) 113 if(vis[i]) 114 printf("%d ",i); 115 putchar('\n'); 116 } 117 } 118 119 int main(){ 120 #ifndef ONLINE_JUDGE 121 freopen("data.in","r",stdin); 122 freopen("data.out","w",stdout); 123 #endif 124 prework(); 125 mainwork(); 126 return 0; 127 } 128
//2019.6.9更新 后面这些是算分考试前补上的...(网络流建模真是有趣(nan)啊
试题九:方格取数问题
在方格中取出若干个数,要求这些数不能相邻,然后希望这些数的总和最大
【建模分析】
黑白染色之后,把黑色当成收益,然后白色当成损失,就变成买仪器那个题了。最大权闭合图。
试题十:餐巾计划问题
一个餐厅在相继的 N天里,每天需用的餐巾数不尽相同。假设第 i 天需要 ri块餐巾( i=1,2,...,N)。餐厅可以购买新的餐巾,每块餐巾的费用为 ppp 分;或者把旧餐巾送到快洗部,洗一块需 m 天,其费用为 f 分;或者送到慢洗部,洗一块需 n(n>m),其费用为 s 分(s<f)。
每天结束时,餐厅必须决定将多少块脏的餐巾送到快洗部,多少块餐巾送到慢洗部,以及多少块保存起来延期送洗。但是每天洗好的餐巾和购买的新餐巾数之和,要满足当天的需求量。
试设计一个算法为餐厅合理地安排好 N天中餐巾使用计划,使总的花费最小。编程找出一个最佳餐巾使用计划。
【建模分析】
可以分成需求和供应两个方面:
需求的话比较简单,就是每天需要的餐巾数目
然后供应的话,有几个来源:直接买;快洗部n天前的,慢洗部m天前的;昨天剩下的。
把毛巾分成今天消耗的和今天得到的,也就是把一天拆成Xi,Yi两个点
然后 s -> Xi = ai,0 (每天的餐巾数) 表示今天产生了这么多的脏毛巾
s -> Yi = inf,p 表示买的餐巾
对于今天的脏毛巾,有三个流向:
Xi -> Yi+n = inf,fp
Xi -> Yi+m = inf,fp
Xi -> Xi+1 = inf,0
分别表示快洗、慢洗和留到明天
Yi -> t = ai,0 表示今天用的毛巾数量,如果流满了说明满足条件,不然就不满足条件
第十一题
航空路线
给定一张航空图,图中顶点代表城市,边代表 2 城市间的直通航线。现要求找出一条满足下述限制条件的且途经城市最多的旅行路线。
(1)从最西端城市出发,单向从西向东途经若干城市到达最东端城市,然后再单向从东向西飞回起点(可途经若干城市)。
(2)除起点城市外,任何城市只能访问 1 次。
对于给定的航空图,试设计一个算法找出一条满足要求的最佳航空旅行路线。
【建模分析】
因为是无向图,所以其实就是找两条从s到t的不相交航线
不相交也就是一个节点只走一次,那么就可以把每个城市拆成一个出点一个入点,然后入点到出点之间流量为1.
然后特殊的在1和n都改成流量为2就可以了。
第十三题
星球转移
有n个空间站,然后m艘飞船,每个空间站有容量hi,飞船有固定路线,问要多少天才可以把所有人运过去
【建模分析】
分层网络流,可以通过按照时间拆分点,同时为了保证容量,在入点和出点之间加上hi的限制
然后飞船能走的星球之间,也要在出点和入点之间连上边,然后枚举加点,直到最大流=Max
第十六题
数字梯形
一个梯形的数字,最上面一层有m个数字,求m条路径:
(1) 都不相交 (2) 允许点相交 (3) 允许边相交
【建模分析】
拆点拆成容量1;把容量1的限制去掉;把上一层到下一层的容量为1的限制去掉
第十七题
运输问题
超级经典的费用流,跳过
第十八题
分配问题
也挺经典的,但是差点看错题看成修车那个题。
修车那个思路也还蛮好的。