zoukankan      html  css  js  c++  java
  • POWOJ 1739: 魔术球问题 DAG最小路径覆盖转最大流

    1739: 魔术球问题

    题意:

    假设有n根柱子,现要按下述规则在这n根柱子中依次放入编号为1,2,3,...的球。 (1)每次只能在某根柱子的最上面放球。 (2)在同一根柱子中,任何2个相邻球的编号之和为完全平方数。 试设计一个算法,计算出在n根柱子上最多能放多少个球。对于给定的n,计算在n根柱子上最多能放多少个球。

    tags: 对大佬来说应该是很素的一道题,但某还是花了好多时间才做出来。。 一开始连建图都有点懵,然后最小路径还是新概念,最大匹配也不太懂,最大流倒是会一点。 然后要打印答案,也不会,或者说最小路径该怎么从网络流中反映出来,这没怎么搞懂。

    可以参考 百度文库,讲的很详细。

    1、首先要想到怎么建图,然后把最小路径覆盖转化为二分图最大匹配,再化为最大流求解。

    2、某是二分答案,每次跑一遍网络流,判断最小路径覆盖数是否小于等于柱子数 。  对于i<j,且 i+j是完全平方数, 建二分图,即连接点 i 和点 m+j 。最后1~m点连接源点,m+1~2*m点连接汇点。   在二分图中求出最大匹配数,DAG中的最小路径覆盖数 = DAG图中的节点数 - 相应二分图中的最大匹配数。 最后搜索打印答案。

    3、本来二分应该要比枚举快,但这题,枚举可以利用上次的残余网络,而二分每次要重新建图,反而更慢。

    建模分析
    由于是顺序放球,每根柱子上的球满足这样的特征,即下面的球编号小于上面球的编号。抽象成图论,把每个球看作一个顶点,就是编号较小的顶点向编号较大的顶点连接边,条件是两个球可以相邻,即编号之和为完全平方数。每根柱子看做一条路径,N根柱子要覆盖掉所有点,一个解就是一个路径覆盖。

    //  1739: 魔术球问题
    #include<bits/stdc++.h>
    using namespace std;
    #pragma comment(linker, "/STACK:102400000,102400000")
    #define rep(i,a,b) for (int i=a;i<=b;i++)
    #define per(i,b,a) for (int i=b;i>=a;i--)
    #define mes(a,b)  memset(a,b,sizeof(a))
    #define INF 0x3f3f3f3f
    typedef long long ll;
    const int N = 100005;
    
    int n, tot, dis[N], head[N], cur[N], remain[N<<1], path[N];
    bool vis[N];
    struct Edge{int to, next;}e[N<<1];
    void Addedge(int u,int v,int w) {
        e[tot]={v,head[u]}; remain[tot]=w;  head[u]=tot++;
        e[tot]={u,head[v]}; remain[tot]=0;  head[v]=tot++;  //反向流量初始为 0
    }
    
    bool bfs(int st, int ed)    // bfs()查询 s到 t是否还有增广路径
    {
        for(int i=st; i<=ed; i++) cur[i]=head[i];
        mes(dis, -1);
        queue<int > q;
        q.push(st);  dis[st]=0;
        while(!q.empty())
        {
            int u=q.front();  q.pop();
            for(int i=head[u]; i!=-1; i=e[i].next) {
                int to=e[i].to;
                if(dis[to]==-1 && remain[i]) {  //remain[]<0 也可
                    q.push(to);
                    dis[to]=dis[u]+1;
                    if(to==ed) return true;
                }
            }
        }
        return false;
    }
    int dfs(int now, int ed, int flow)      // flow表示能够提供给当前结点的最大流量
    {
        if(now==ed || flow==0) return flow;
        int s, ans=0;
        for(int &i=cur[now]; i!=-1; i=e[i].next) {  // &i=cur[now],改变了cur[now],让下一次的dfs不重复走
            int to=e[i].to;
            if(dis[to]==dis[now]+1 && (s=dfs(to,ed,min(flow,remain[i]))) ) {
                if(to>(ed-1)/2) path[now]=to-(ed-1)/2, vis[path[now]]=1;    // 记录路径
                ans+=s, flow-=s;            // ans表示当前结点可以消耗的最大流量
                remain[i]-=s, remain[i^1]+=s;   // 更新残余流量
                if(flow==0) break;
            }
        }
        return ans;
    }
    int dinic(int m)
    {
        int st=0, ed=2*m+1, s, ans=0;
        while(bfs(st,ed))
            while(s=dfs(st,ed,INF))
                ans+=s;         // ans即是最大流,也是二分图最大匹配数
        return ans;
    }
    int solve(int m)
    {
        int st=0, ed=2*m+1;
        tot=0;  mes(head,-1);   mes(vis,false);   mes(path, 0);
        rep(i,1,m)  rep(j,i+1,m) {        //加边,建二分图
            if((int)sqrt(i+j)==sqrt(i+j)) Addedge(i,m+j,1);
        }
        rep(i,1,m) {
            Addedge(st,i,1), Addedge(m+i,ed,1);
        }
        return m-dinic(m);      // DAG中的最小路径覆盖数 = DAG图中的节点数 - 相应二分图中的最大匹配数
        // 二分图中,最小边覆盖 = 图中点的个数 - 最大匹配数=最大独立集 , 最大匹配数 = 左边匹配点 + 右边未匹配点
    }
    
    void print(int ans)     // 打印答案
    {
        printf("%d
    ", ans);
        rep(i,1,ans) if(vis[i]==0) {
            printf("%d", i);
            int pos=i;
            while(path[pos]) { printf(" %d", path[pos]); pos=path[pos]; }
            puts("");
        }
    }
    int main()
    {
        scanf("%d", &n);
        int l=n, r=2000, ans=0;
        while(l<=r) {
            int mid=(l+r)>>1;
            int ans1=solve(mid);
            if(ans1<=n) {    // 每次跑一遍网络流,判断最小路径覆盖数是否小于等于柱子数
                if(ans1==n)  ans=max(ans, mid);
                l=mid+1;
            }
            else r=mid-1;
        }
        solve(ans);
        print(ans);
    
        return 0;
    }
    View Code
  • 相关阅读:
    Activity具体解释(生命周期、以各种方式启动Activity、状态保存,全然退出等)
    StringBuffer和StringBuilder使用方法比較
    python 多线程编程
    八大排序算法总结
    腾讯面试
    顶尖的个人作品集站点设计赞赏
    MATLAB新手教程
    ThreadPoolExecutor使用介绍
    linux diff具体解释
    Android借助Application重写App的Crash(简易版)
  • 原文地址:https://www.cnblogs.com/sbfhy/p/6680024.html
Copyright © 2011-2022 走看看