• 刷题总结——魔术球问题(ssoj最小路径覆盖+网络流)


    题目:

    题目描述

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

    输入格式

    输入文件第 1 行有 1 个正整数 n(1<n<60),表示柱子数。

    输出格式

    输出 n 根柱子上最多能放的球数。

    样例数据 1

    输入  [复制]

     
    4

    输出

    11

    备注

    【样例说明】
    最多能放 11 个球,下面 4 行,每行是一根柱子上的球的编号。
    1 8
    2 7 9
    3 6 10
    4 5 11

    【思考以下输出样式】
    将 n 根柱子上最多能放的球数以及相应的放置方案输出到文件中。
    文件的第一行是球数。接下来的 n 行,每行是一根柱子上的球的编号。

    题解:

      首先可以想到这道题的策略肯定是向上枚举球的数量然后判断····

      建图方法是:如果对于i<j有i+j为一个完全平方数,连接一条有向边(i,j)。该图是有向无环图,求最小路径覆盖。如果刚好满足最小路径覆盖数等于N,那么A是一个可行解,在所有可行解中找到最大的A,即为最优解。最小路径覆盖相关知识点如下:

      有向无环图最小不相交路径覆盖

      定义:用最少的不相交路径覆盖所有顶点。

      定理:把原图中的每个点V拆成Vx和Vy,如果有一条有向边A->B,那么就加边Ax-By。这样就得到了一个二分图,最小路径覆盖=原图的节点数-新图最大匹配。

      简单证明:一开始每个点都独立的为一条路径,总共有n条不相交路径。我们每次在二分图里加一条边就相当于把两条路径合成了一条路径,因为路径之间不能有公共点,所以加的边之间也不能有公共点,这就是匹配的定义。所以有:最小路径覆盖=原图的节点数-新图最大匹配。

      因此每次枚举新的点加直接和之前的点枚加边即可···令外每次不用重新在图上跑网络流,记录一个group表示柱子数,和枚举的点数一起加减,然后用group减去新跑的流即可,这样就相当于枚举的点数减去在新图上完全新跑出的流(看不懂的看代码就可以了),即为最小路径覆盖

    代码:

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cmath>
    #include<ctime>
    #include<cctype>
    #include<cstring>
    #include<string>
    #include<algorithm>
    using namespace std;
    const int inf=1e+9;
    const int N=100005;
    int src=0,des=10000;
    int group,num,n;
    int first[N],next[N*2],go[N*2],rest[N*2],tot=1,lev[N],cur[N];
    inline void comb(int a,int b,int c)
    {
      next[++tot]=first[a],first[a]=tot,go[tot]=b,rest[tot]=c;
      next[++tot]=first[b],first[b]=tot,go[tot]=a,rest[tot]=0;
    }
    inline bool bfs()
    {
      for(int i=src;i<=des;i++)  cur[i]=first[i],lev[i]=-1;
      static int que[N],tail,u,v;
      que[tail=1]=src;
      lev[src]=0;
      for(int head=1;head<=tail;head++)
      {
        u=que[head];
        for(int e=first[u];e;e=next[e])
        {
          if(lev[v=go[e]]==-1&&rest[e])
          {
            lev[v]=lev[u]+1;
            que[++tail]=v;
            if(v==des)  return true;
          }
        }
      }
      return false;
    }
    inline int dinic(int u,int flow)
    {
      if(u==des)
        return flow;
      int res=0,delta,v;
      for(int &e=cur[u];e;e=next[e])
      {
        if(lev[v=go[e]]>lev[u]&&rest[e])
        {
          delta=dinic(v,min(flow-res,rest[e]));
          if(delta)
          {
            rest[e]-=delta;
            rest[e^1]+=delta;
            res+=delta;
            if(res==flow)  break;
          }
        }
      }
      if(flow!=res)  lev[u]=-1;
      return res;
    }
    inline void maxflow()
    {
      while(bfs())
        group-=dinic(src,inf);
    }
    int main()
    {
      //freopen("a.in","r",stdin);
      scanf("%d",&n);
      while(true)
      {
        group++,num++;
        for(int i=1;i<num;i++)
          if(sqrt(i+num)==(int)sqrt(i+num))
            comb(i,num+5000,1);
        comb(num+5000,des,1);
        comb(src,num,1); 
        maxflow();
        if(group>n)  break;
      }
      cout<<num-1<<endl;
      return 0;
    }
  • 相关阅读:
    《零基础入门学习Python》学习过程笔记【012列表的常用函数,逻辑关系,+,*,in,列表推导式】
    鱼C工作室《零基础入门学习Python》 学习过程笔记【011列表类的方法】
    鱼C工作室《零基础入门学习Python》学习过程笔记记录第一天 001-010
    笨方法学python(本文为阅读时从此书摘录的笔记) 第六天(留坑)
    笨方法学python(本文为阅读时从此书摘录的笔记) 第五天
    DAY 165 创建虚拟环境01
    DAY 164 SVN常用命令
    DAY 163 Yaml语法使用
    DAY 162 linux sudo 命令
    DAY 161 pycharm同步代码到linux
  • 原文地址:https://www.cnblogs.com/AseanA/p/7465083.html
Copyright © 2020-2023  润新知