网络流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 }

时间: 2024-08-04 08:26:43

网络流24题刷题记录的相关文章

LeetCode算法题--刷题第一天

Given an array of integers, return indices of the two numbers such that they add up to a specific target. You may assume that each input would have exactly one solution, and you may not use the same element twice. (译:给定一个整数数组,返回两个数字的索引,使它们相加得到一个特定目标值

oj刷题——第十五周C++习题 对象转换

Description 定义一个Teacher(教师)类(教师号,姓名,性别,薪金)和一个Student(学生)类(学号,姓名,性别,成绩),二者有一部分数据成员是相同的,num(号码),name(姓名),sex(性别).编写程序,将一个Student对象(学生)转换为Teacher(教师)类,只将以上3个相同的数据成员移植过去.可以设想为: 一位学生大学毕业了,留校担任教师,他原有的部分数据对现在的教师身份来说仍然是有用的,应当保留并成为其教师数据的一部分. Input 一个教师的信息和一个学

第十五周oj刷题——Problem B: 矩形类定义【C++】

Description 定义一个矩形类,数据成员包括左下角和右上角坐标,定义的成员函数包括必要的构造函数.输入坐标的函数,以及计算并输出矩形面积的函数.要求使用提示中给出的测试函数并不得改动. Input 四个数,分别表示矩形左下角和右上角顶点的坐标,如输入3.7 0.4 6.5 4.9,代表左下角坐标为(3.7, 0.4),右上角坐标为(6.5, 4.9). Output 输出一共有3行(请参考提示(hint)中的main函数): 第一行:由输入的坐标确定的矩形对象p1的面积 第二行:由对象复

【网络流24题】

网络流 网络流24题 [最小路径覆盖问题] 关于输出路径,因为即使有反向弧经过左侧点也一定会改变左侧点的去向,若没连向右侧就会被更新到0,所以不用在意. mark记录有入度的右侧点,然后从没入度的右侧点开始把整条路径输出来即可. #include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int maxn=100000,inf=0x3f3f3f3f; int n,m,

【线性规划与网络流 24题】完成度(1/24)

PS:SDOI2016 Round1滚粗后蒟蒻开始做网络流来自我拯救(2016-04-11再过几天就要考先修课,现在做网络流24题貌似没什么用←退役节奏) 做的题目将附上日期,见证我龟速刷题. 1.飞行员配对方案问题 2016-04-11 二分图最大匹配问题,更新了一下$Dinic$模板,带上了当前弧优化和多路增广.这道题输出方案有很多种,可是没有special judge,所以没有A,但方案数是对的.合法的输出方案只能用匈牙利算法解决. #include<queue> #include<

LibreOJ #6001. 「网络流 24 题」太空飞行计划 最大权闭合图

#6001. 「网络流 24 题」太空飞行计划 内存限制:256 MiB时间限制:1000 ms标准输入输出 题目类型:传统评测方式:Special Judge 上传者: 匿名 提交提交记录统计讨论测试数据 题目描述 W 教授正在为国家航天中心计划一系列的太空飞行.每次太空飞行可进行一系列商业性实验而获取利润.现已确定了一个可供选择的实验集合 E={E1,E2,?,Em} E = \{ E_1, E_2, \cdots, E_m \}E={E?1??,E?2??,?,E?m??},和进行这些实验

LiberOJ #6013. 「网络流 24 题」负载平衡 最小费用最大流 供应平衡问题

#6013. 「网络流 24 题」负载平衡 内存限制:256 MiB时间限制:1000 ms标准输入输出 题目类型:传统评测方式:文本比较 上传者: 匿名 提交提交记录统计讨论测试数据 题目描述 G 公司有 n nn 个沿铁路运输线环形排列的仓库,每个仓库存储的货物数量不等.如何用最少搬运量可以使 n nn 个仓库的库存数量相同.搬运货物时,只能在相邻的仓库之间搬运. 输入格式 文件的第 1 11 行中有 1 11 个正整数 n nn,表示有 n nn 个仓库.第 2 22 行中有 n nn 个

LiberOJ #6000. 「网络流 24 题」搭配飞行员 最大匹配

#6000. 「网络流 24 题」搭配飞行员 内存限制:256 MiB时间限制:1000 ms标准输入输出 题目类型:传统评测方式:文本比较 上传者: 匿名 提交提交记录统计讨论测试数据 题目描述 飞行大队有若干个来自各地的驾驶员,专门驾驶一种型号的飞机,这种飞机每架有两个驾驶员,需一个正驾驶员和一个副驾驶员.由于种种原因,例如相互配合的问题,有些驾驶员不能在同一架飞机上飞行,问如何搭配驾驶员才能使出航的飞机最多. 因为驾驶工作分工严格,两个正驾驶员或两个副驾驶员都不能同机飞行. 输入格式 第一

LiberOJ #6002. 「网络流 24 题」最小路径覆盖

#6002. 「网络流 24 题」最小路径覆盖 内存限制:256 MiB时间限制:1000 ms标准输入输出 题目类型:传统评测方式:Special Judge 上传者: 匿名 提交提交记录统计讨论测试数据 题目描述 给定有向图 G=(V,E) G = (V, E)G=(V,E).设 P PP 是 G GG 的一个简单路(顶点不相交)的集合.如果 V VV 中每个顶点恰好在 P PP 的一条路上,则称 P PP 是 G GG 的一个路径覆盖.P PP 中路径可以从 V VV 的任何一个顶点开始,