标签:
题目一:飞行员配对方案问题
一群飞行员和另一群飞行员之间有的可以配对,有的不能配对,求最多可以配多少对?
典型二分图最大匹配问题。可以用匈牙利算法 或者 加上源点汇点之后跑最大流。【感觉第二个打错的概率还低一些】。
【还是介绍一下匈牙利算法吧】【看白书大法好!】
从左边(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 }
标签:
原文地址:http://www.cnblogs.com/Robert-Yuan/p/4685547.html