网络流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;
}
原文地址:https://www.cnblogs.com/zhouzhendong/p/LuoguP2765.html