本来是应该用最大流做的的
但是实际上还有一种方法可以得到答案:
现在如果有n个柱子,要放数i,从第一个柱子开始试,试到能放的那个柱子,就把i放进去,如果n个柱子都不行,就停止计算,得到最大值。
这其实是一个贪心的证明,事实证明最大流与贪心的答案是吻合的。同样这是一个方案数多样化的题目,鉴于没有Special Judge,所以过不了也是正常,不过不用管它。
这道题的标准做法应该是最小路径覆盖,做成一个二分图,进而求最大流。
下面copy一下ByVoid大神的分析:
【建模方法】
枚举答案A,在图中建立节点1..A。如果对于i<j有i+j为一个完全平方数,连接一条有向边(i,j)。该图是有向无环图,求最小路径覆盖。如果刚好满足最小路径覆盖数等于N,那么A是一个可行解,在所有可行解中找到最大的A,即为最优解。
具体方法可以顺序枚举A的值,当最小路径覆盖数刚好大于N时终止,A-1就是最优解。
【建模分析】
由于是顺序放球,每根柱子上的球满足这样的特征,即下面的球编号小于上面球的编号。抽象成图论,把每个球看作一个顶点,就是编号较小的顶点向编号较大的顶点连接边,条件是两个球可以相邻,即编号之和为完全平方数。每根柱子看做一条路径,N根柱子要覆盖掉所有点,一个解就是一个路径覆盖。
最小路径覆盖数随球的数量递增不递减,满足单调性,所以可以枚举答案(或二分答案),对于特定的答案求出最小路径覆盖数,一个可行解就是最小路径覆盖数等于N的答案,求出最大的可行解就是最优解。本问题更适合枚举答案而不是二分答案,因为如果顺序枚举答案,每次只需要在残量网络上增加新的节点和边,再增广一次即可。如果二分答案,就需要每次重新建图,大大增加了时间复杂度。
然后放出贪心代码,其实比最大流短得多:
#include <iostream>
#include <vector>
#include <cmath>
#define MaxN 60
using namespace std;
vector<int> a[MaxN];
inline bool suit(const int &x,const int &y)
{
int n=x+y;
return !(int(sqrt(n))<sqrt(n));
}
int main()
{
freopen("ball.in","r",stdin);
freopen("ball.out","w",stdout);
int n,ans=1;
bool flag;
cin>>n;
for(int i=1;i<=n;i++)
a[i].push_back(0);
for(;;)
{
flag=0;
for(int i=1;i<=n;i++)
if(suit(ans,a[i][a[i][0]])||!a[i][0])
{
flag=1,
a[i][0]++,
a[i].push_back(ans),
ans++;
break;
}
if(!flag) break;
}
cout<<ans-1<<endl;
for(int i=1;i<=n;i++,printf("\n"))
for(int j=1;j<=a[i][0];j++)
printf("%d ",a[i][j]);
return 0;
}