zoukankan      html  css  js  c++  java
  • 网络流24题 第四题

    欢迎访问~原文出处——博客园-zhouzhendong

    去博客园看该题解

     


     

    题目传送门 - 洛谷2765

    题意概括

    假设有n根柱子,现要按下述规则在这n根柱子中依次放入编号为1,2,3,...的球。

      (1)每次只能在某根柱子的最上面放球。

      (2)在同一根柱子中,任何2个相邻球的编号之和为完全平方数。

    试设计一个算法,计算出在n根柱子上最多能放多少个球。

    (哈哈,题意直接复制,原题够简略了)

    数据范围 - 不详


    题解

      这一题是一个有点难的题。

      前置技能 - 网络流算法(传送门)

      我们先从简单的情况出发:给你m个数字,让你来做,最少需要多少个位置?

      这个怎么做?

      答案是二分图匹配。

      我们对于每一个点(数字),拆成两个点,一个是二分图中左边一排的,一个是二分图中右边一排的,然后就是——

      有向无环图最小路径覆盖问题

      至于该图中要连的边,那么就是根据题目规定的要求来的:如果i<j且(i+j)为一个完全平方数,那么出点i和入点j就要连上一条边。(注意这里所的出点和入点是两个不同的点集)

      然后跑二分图匹配即可求出最大匹配数,然后,

      需要的位置数 = 最少路径条数

             = 总点数 - 二分图匹配数

      那么对于该题,我们又不知道有几个数字!

      相反,叫我们求的是有几个数字!


      思路1 :既然这样,那么我们可以二分答案啊,对于每一次,跑一遍匈牙利。

      只要证明n增长的同时,ans也是单调递增的。

      那么也可以去证明ans减少的时候n是单调不上升的。

      其实很简单:设当ans = k + 1时,我们有一个方案,使得所有的ans个数字都合法放置,那么第k + 1个数字一定是最后放上去的。对于ans = k的情况,只需要把第k + 1个数字拿掉,那么就至少满足了前面的结论。

      但是实际上这个方法的时间复杂度非常玄。

      每次要重新构图,还要跑匈牙利算法,虚啊!


      思路2 :暴力搜索,网络流SAP动态增广

      那么网络流就可以解决这个问题了。

      我们一个一个枚举点,每加入一个点,建立相应的边,然后用网络流跑一跑增广路,这样可以快很多。

      但是细节非常多。

      最后还要还原路径,具体方法就是顺着搜过去,不用寻找前驱节点(因为数字小的一定先放),只要一边找后继节点一边输出就可以了。

      至于要开多大,题目没说,我也难以估计,所以,最终我采用的方法是:试数据。

      在尝试不到10次(刚好9次)之后,终于得到了AC,最慢点时耗为388MS的优(ji)越(man)的时间。

      接下来放代码。


    代码

    #include <cstring>
    #include <algorithm>
    #include <cstdio>
    #include <cstdlib>
    #include <cmath>
    using namespace std;
    const int maxR=25000+5,N=maxR*2,M=4000000,Inf=1<<25;
    struct Edge{
        int x,y,cap,flow,nxt;
    };
    struct Gragh{
        int cnt,fst[N],dist[N],num[N],cur[N],p[N];
        int s,t,n,MaxFlow;
        Edge e[N];
        void set(int S,int T,int n){
            s=S,t=T,cnt=1,(*this).n=n;
            memset(fst,0,sizeof fst);
        }
        void add(int a,int b,int c){
            e[++cnt].x=a,e[cnt].y=b,e[cnt].cap=c,e[cnt].flow=0;
            e[cnt].nxt=fst[a],fst[a]=cnt;
            e[++cnt].x=b,e[cnt].y=a,e[cnt].cap=0,e[cnt].flow=0;
            e[cnt].nxt=fst[b],fst[b]=cnt;
        }
        int pre_SAP(){
            MaxFlow=0;
            for (int i=2;i<=cnt;i++)
                e[i].flow=0;
            memset(num,0,sizeof num);
            memset(p,0,sizeof p);
            for (int i=1;i<=n;i++)
                num[dist[i]]++,cur[i]=fst[i];
        }
        int Augment(int &point){
            int ex_Flow=Inf;
            for (int i=t;i!=s;i=e[p[i]].x)
                if (e[p[i]].cap-e[p[i]].flow<=ex_Flow)
                    ex_Flow=e[p[i]].cap-e[p[i]].flow,point=e[p[i]].x;
            for (int i=t;i!=s;i=e[p[i]].x)
                e[p[i]].flow+=ex_Flow,e[p[i]^1].flow-=ex_Flow;
            return ex_Flow;
        }
        int SAP(){
            int x=s,y;
            memset(dist,0,sizeof dist);
            memset(num,0,sizeof num);
            for (int i=1;i<=n;i++)
                num[dist[i]]++,cur[i]=fst[i];
            while (dist[s]<=n){
                if (x==t){
                    MaxFlow+=Augment(x);
                    continue;
                }
                bool found=0;
                for (int i=cur[x];i!=0&&!found;i=e[i].nxt)
                    if (dist[e[i].y]+1==dist[x]&&e[i].cap>e[i].flow)
                        p[e[i].y]=cur[x]=i,x=e[i].y,found=1;
                if (found)
                    continue;
                int d=n+1;
                for (int i=fst[x];i;i=e[i].nxt)
                    if (e[i].flow<e[i].cap)
                        d=min(d,dist[e[i].y]+1);
                if (!(--num[dist[x]]))
                    return MaxFlow;
                num[dist[x]=d]++,cur[x]=fst[x];
                if (x!=s)
                    x=e[p[x]].x;
            }
            return MaxFlow;
        }
    }g;
    int r=maxR-5,n;
    bool vis[N];
    int main(){
        scanf("%d",&n);
        g.set(r*2+1,r*2+2,r*2+2);
        int ans;
        g.pre_SAP();
        for (int i=1;i<=r;i++){
            g.add(g.s,i,1);
            g.add(i+r,g.t,1);
            for (int j=1;j<i;j++){
                int s=sqrt(i+j);
                if (s*s==i+j)
                    g.add(j,i+r,1);
            }
            int nowFlow=g.SAP();
            if (i-nowFlow>n){
                ans=i-1;
                break;
            }
        }
        printf("%d
    ",ans);
        int de=ans+1;
        for (int i=2;i<=g.cnt;i++)
            if (g.e[i].x==de||g.e[i].y==de||g.e[i].x==de+r||g.e[i].y==de+r)
                g.e[i].cap=g.e[i].flow=0;
        g.pre_SAP();
        int x=g.SAP();
        memset(vis,0,sizeof vis);
        vis[ans+1]=1;
        for (int i=1;i<=ans;i++){
            if (vis[i])
                continue;
            int x=i;
            while (1){
                printf("%d ",x);
                vis[x]=1;
                bool found=0;
                for (int i=g.fst[x];i;i=g.e[i].nxt){
                    if (i%2==1)
                        continue;
                    if (!vis[g.e[i].y-r]&&g.e[i].cap==g.e[i].flow){
                        x=g.e[i].y-r;
                        found=1;
                        break;
                    }
                }
                if (!found)
                    break;
            }
            puts("");
        }
        return 0;
    }
  • 相关阅读:
    POJ 2253 Frogger
    POJ 2387
    codevs3981动态最大子段和(线段树)
    P3398仓鼠(LCA)
    codevs1036商务旅行(LCA)
    codevs3728联合权值(LCA)
    P3390矩阵快速幂
    codevs1574广义斐波那契数列
    POJ3070Fibonacci
    P3379最近公共祖先(LCA)
  • 原文地址:https://www.cnblogs.com/zhouzhendong/p/LuoguP2765.html
Copyright © 2011-2022 走看看