网络流二十四题之四 —— 魔术球问题(BALL)

5 篇文章 0 订阅
24 篇文章 0 订阅

最小路径覆盖问题


Description

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


Input

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


Output

程序运行结束时,将 n 根柱子上最多能放的球数以及相应的放置方案输出。
文件的第一行是球数。
接下来的 n 行,每行是一根柱子上的球的编号。


Sample Input

4


Sample Output

11
1 8
2 7 9
3 6 10
4 5 11


Solution


方法一(网络流):

每根柱子互不干扰,可以把每根柱子看成一条路径——这不就成了最小路径覆盖问题了吗???

将相加为完全平方数的两个数连一条边,求解最小路径覆盖问题就可以解决 n0 个球最少需要多少根柱子的问题。

然后依次枚举 n0 ,直到所需柱子的个数大于 n ,即可得出答案(这里用枚举的原因是,每次不需要重新求解最大流)。


方法二(贪心):

还有一种贪心的方法。
现在如果有 T 个柱子,要放数 i ,从第一个柱子开始试,试到能放的那个柱子,就把 i 放进去,如果 T 个柱子都不行,就再多增加一个柱子放 i

易知,这样得出的答案为 f(n)={(n21)/2+n   n1(mod 2)(n22)/2+n   n0(mod 2)

下面,证明一下这个贪心的正确性。

n0(mod 2) ,假设 ans(n)>f(n) ,则 ans(n) 最小为 f(n)+1

此时,这 ans(n) 个数为 123(n21)/2+n+1

易知在前 n+1 个数中,最大的两个数之和为
(n21)/2+n+1+(n21)/2+n=n2+2n<(n+1)2

最小的两个数之和为
[(n21)/2+n+1(n+1)+1]+[(n21)/2+n+1(n+1)+2]=n2+2>n2

即这 n+1 个数任意两个数之和都夹在两个完全平方数之间,即任意两个数之和都不为完全平方数。
所以,这 n+1 个数都不能放在同一根柱子上,与只有 n 根柱子相矛盾。

同理,当 n1(mod 2) 时,也可以用反证法证明这个贪心的正确性。

综上所述,这个贪心是正确的。


Code(只给出网络流版本)

[cpp]
  1. #include <iostream>  
  2. #include <cstdio>  
  3. #include <cstring>  
  4. #include <cmath>  
  5. #include <queue>  
  6.   
  7. #define Min(x,y) ((x)<(y)?(x):(y))  
  8. #define Max(x,y) ((x)>(y)?(x):(y))  
  9.   
  10. using namespace std;  
  11.   
  12. const int INF=0x3f3f3f3f;  
  13.   
  14. int m,n,s,t=100000-1,cnt,nx;  
  15. int weight;  
  16.   
  17. int low[1000100],head[1000100],nxt[1000100],data[1000100];  
  18. int dis[1000100];  
  19. bool vis[1000100];  
  20. bool he[1000010];  
  21. queue<int>q;  
  22.   
  23. void add(int x,int y,int z){  
  24.     nxt[cnt]=head[x];data[cnt]=y;low[cnt]=z;head[x]=cnt++;  
  25.     nxt[cnt]=head[y];data[cnt]=x;low[cnt]=0;head[y]=cnt++;   
  26. }  
  27.   
  28. bool BFS(){  
  29.     memset(dis,-1,sizeof dis);  
  30.     q.push(s);dis[s]=0;  
  31.     while(!q.empty()){  
  32.         int now=q.front();q.pop();  
  33.         for(int i=head[now];i!=-1;i=nxt[i])  
  34.             if(low[i]&&dis[data[i]]<0){dis[data[i]]=dis[now]+1;q.push(data[i]);}  
  35.     }  
  36.     return dis[t]>0;  
  37. }  
  38.   
  39. int dfs(int now,int flow){  
  40.     if(now==t)return flow;  
  41.     int Flow;  
  42.     for(int i=head[now];i!=-1;i=nxt[i]){  
  43.         if(low[i]&&dis[data[i]]==dis[now]+1){  
  44.             if(Flow=dfs(data[i],Min(flow,low[i]))){  
  45.                 low[i]-=Flow;  
  46.                 low[i^1]+=Flow;  
  47.                 return Flow;  
  48.             }  
  49.         }  
  50.     }  
  51.     return 0;  
  52. }  
  53.   
  54. void dfs2(int now){  
  55.     printf(”%d ”,now);  
  56.     vis[now]=true;  
  57.     for(int i=head[now];i!=-1;i=nxt[i]){  
  58.         if(!vis[data[i]]&&data[i]!=s&&data[i]!=t&&data[i]>n&&!low[i])dfs2(data[i]-2000);  
  59.     }  
  60. }  
  61.   
  62. int work(int front){  
  63.     weight++;  
  64.     n=front;  
  65. //  for(int i=0;i<cnt;i+=2){  
  66. //      low[i]=1;  
  67. //      low[i+1]=0;  
  68. //  }  
  69.     for(int i=1;i<front;i++){  
  70.         if(he[i+front])add(i,front+2000,1);  
  71.     }  
  72.     add(s,front,1);add(front+2000,t,1);  
  73.     while(BFS()){  
  74.         int flag;  
  75.         while(flag=dfs(s,INF)){  
  76.             weight-=flag;  
  77.         }  
  78.     }  
  79.     return weight;  
  80. }  
  81.   
  82. void work2(int front){  
  83.     memset(head,-1,sizeof head);  
  84.     cnt=0;  
  85.     n=front;  
  86.     for(int i=1;i<=front;i++)  
  87.         for(int j=i+1;j<=front;j++)  
  88.             if(he[i+j])add(i,j+2000,1);  
  89.     for(int i=1;i<=front;i++){add(s,i,1);add(i+2000,t,1);}  
  90.     while(BFS()){  
  91.         int flag;  
  92.         while(flag=dfs(s,INF));  
  93.     }  
  94. }  
  95.   
  96. int main(){  
  97.     freopen(”ball.in”,“r”,stdin);  
  98.     freopen(”ball.out”,“w”,stdout);   
  99.     memset(head,-1,sizeof head);  
  100.     int ans=0,ll;  
  101.     scanf(”%d”,&nx);  
  102.     for(int i=1;i<=100;i++)  
  103.         he[i*i]=true;  
  104.     for(ll=1;;ll++){  
  105.         int tmp=work(ll);  
  106.         bool flag=(tmp<=nx);  
  107.         if(!flag)break;  
  108.     }  
  109.     printf(”%d\n”,ll-1);  
  110.     work2(ll-1);  
  111.     for(int i=1;i<ll;i++)if(!vis[i]){  
  112.         dfs2(i);  
  113.         printf(”\n”);  
  114.     }  
  115.     return 0;  
  116. }  
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 魔术方法是Python中的特殊方法,它们以双下划线开头和结尾,例如__init__、__str__、__add__等。这些方法可以在类的实例化、运算符重载、属性访问等方面提供特殊的行为。 __init__方法是一个特殊的构造函数,用于初始化类的实例。__str__方法用于返回对象的字符串表示形式,可以通过print函数输出。__add__方法用于重载加法运算符,可以实现自定义的加法操作。其他常用的魔术方法还包括__eq__、__lt__、__gt__等,用于比较运算符的重载。 学习魔术方法可以让我们更好地理解Python面向对象编程的特性,提高代码的可读性和可维护性。 ### 回答2: 魔术方法是Python中最有趣且也是最强大的概念之一。魔术方法(也称为特殊方法或双下划线方法)是一些特殊的方法,它们以双下划线(__)开头和结尾,并具有特定的名称。 这些特殊方法可以为我们提供许多有用的功能,例如重载操作符,处理类的属性,实现自定义迭代器,使用描述符等。 下面是一些常见的魔术方法: __init__:这是最常见的魔术方法。当创建一个实例时,它会被自动调用。它用于初始化对象的属性。 __str__:当你想要将一个对象转换成字符串时,这个方法会被调用。如果你不指定__str__方法,Python默认会使用对象的类名和内存地址来表示对象。 __repr__:这个方法和__str__方法类似,也是用于将对象转换成字符串。但是__repr__方法在调试时有很大的作用,因为它返回的字符串可以用来唯一地标识对象。 __len__:这个方法可以返回对象的长度。例如,如果你想获取一个字符串的长度,你可以使用len("hello"),在底层,它实际上是调用了字符串对象的__len__方法。 __getattr__和__setattr__:这些方法允许你动态地获取和设置对象的属性。当你访问一个不存在的属性时,__getattr__方法会被调用。当你设置一个属性时,__setattr__方法会被调用。 __call__:这个方法允许你将对象作为函数调用。当你调用一个对象时,Python实际上是在调用对象的__call__方法。 除了上面列举的方法,还有许多其他的魔术方法,例如__cmp__,__hash__,__iter__等等。学习这些魔术方法将使你能够更好地理解Python的面向对象编程模型。 总之,学习和理解魔术方法是Python面向对象编程中的一个关键概念,因为它们可以帮助你实现更加灵活和强大的代码。如果你想成为一名Python高手,那么深入学习魔术方法是不可避免的。 ### 回答3: Python中的“魔术方法”指的是每个类中定义的特殊方法,它们以双下划线(__)开头和结尾,并且有着特定的用途。通过使用这些魔法方法,我们可以自定义类的行为,并为程序提供更高级别的功能。 以下是Python中常用的一些魔术方法: 1. __init__:这是最常用的魔术方法之一,它用于初始化一个类的对象,以及定义类的属性和方法。 2. __str__:此方法用于返回对象的字符串表示形式,类似于Java中的toString()方法。 3. __repr__:与__str__类似,但是返回的是对象的“官方”字符串表示形式,通常用于调试和开发。 4. __getattr__:当试图访问一个不存在的属性时,此方法被调用。 5. __setattr__:当尝试设置类的属性时,此方法被调用。 6. __delattr__:当尝试删除类的属性时,此方法被调用。 7. __call__:将对象作为函数调用时,此方法被调用。 8. __len__:返回对象的长度。 9. __getitem__:允许通过索引访问对象的元素。 10. __setitem__:允许通过索引设置对象的元素。 11. __delitem__:允许通过索引删除对象的元素。 通过了解和使用这些魔术方法,我们可以编写出更高效、更灵活、更具可读性的Python代码,并且实现类似于内置类型一样的功能。例如,我们可以实现一个自定义列表,类似于Python的list类型,然后使用上述魔术方法来访问、设置和删除元素。同时,我们还可以自定义变量和函数的行为,使我们的Python代码变得更具有表现力和弹性。 总之,了解和掌握Python的魔术方法是Python编程中必不可少的一部分,对于理解和编写实际应用程序非常有价值。在实践中,我们可以根据实际情况选择恰当的魔术方法,从而创建更灵活、更高效的Python类。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

XY20130630

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值