题目描述
有 N N N 个石子堆成一堆。
你可以对石子堆进行拆分操作,具体为选中一堆现有石子堆,将其中的石子分为两堆(不能为空)。
此外,在任何时候,最小石子堆的石子数量都必须严格大于最大石子堆的石子数量的一半。
我们希望,通过将石子堆进行拆分,最终可以满足:
石子堆的总数量尽可能大,不妨设这个总数量的最大可能值为
M
M
M。
满足上一条要求的前提下,最大石子堆石子数量与最小石子堆石子数量之差尽可能小,不妨设这个差的最小可能值为
D
D
D。
请你计算并输出
M
M
M 和
D
D
D 的值。
输入格式
一个正整数 N N N。
输出格式
一行,输出 M M M 和 D D D。
数据范围
2 ≤ N ≤ 200 2≤N≤200 2≤N≤200
输入样例:
22
输出样例:
4 1
样例解释
当 N N N= 22 22 22 时,最多可以拆分为 4 4 4 个石子堆。
以下是可行方案。
方案一:
22
= 8 + 14
= 8 + 7 + 7
= 4 + 4 + 7 + 7
方案二:
22
= 10 + 12
= 10 + 6 + 6
= 4 + 6 + 6 + 6
方案三:
22
= 10 + 12
= 10 + 6 + 6
= 5 + 5 + 6 + 6
可以看出,采用最后一种方案,最大石子堆石子数量与最小石子堆石子数量之差最小,为 6−5=1。
思路:
我们分析会发现我们只能找最多的一堆来拆。
现在我们我们有两堆,a和b,且b<=a,我们拆b。现在我们不知道具体数的范围且我们要满足最小石子堆的石子数量都必须严格大于最大石子堆的石子数量的一半。b拆分的最小值应该是b/2。b/2一定<=a/2的。一定不满足条件。所以我们发现每次我们只能拆最大的一堆。如果所有数都小于等于最大的堆我们就应该停止划分。
现在我们来分类讨论,只有一堆和大于等于二堆的情况
1:如果只有一堆:我们要拆成两堆,设两堆数为x,y且x<=y。x+y=a。拆完后 x>y/2 等价于2x>y.带入x+y=a。我们会得到x>a/3。又由于 x<=y , y=a-x 所以 x<=a/2 。所有x的范围是(a/3,a/2]。
2:如果至少有两堆。我们要先找到一个最大值和一个次大值:a和b 。将a拆分为x+y=a,且x要大于b/2。所以我们的范围是 (max(a/3,b/2),a/2]。
代码如下:
#include<iostream>
#include<algorithm>
using namespace std;
const int N=210;
int n;
int w[N],cnt;// w[]存储每一堆石子数量 cnt是石子堆的个数
int m,d;// 答案M 和 D
int getd()//求最大石子堆石子数量与最小石子堆石子数量之差
{
int x=0,y=N;
for(int i=0;i<cnt;i++)
{
x=max(x,w[i]);
y=min(y,w[i]);
}
return x-y;
}
void dfs()
{
if(cnt>m) m=cnt,d=getd();//现在的石子堆数>上一次的,更新M和D
else if(cnt==m) d=min(d,getd());//相等只更新 D
if(cnt==1)//只有一堆的情况
{
int a=w[0];
for(int x=a/3+1;x<=a/2;x++)
{
w[0]=x;
w[cnt++]=a-x;
dfs();
//恢复现场
w[0]=a;
cnt--;
}
}
else
{
int i=0,j=-1;
for(int k=1;k<cnt;k++)//找到第一,第二大的值的下标
if(w[k]>=w[i]) j=i,i=k;
else if(j==-1||w[k]>w[j]) j=k;
int a=w[i],b=w[j];
if(a>b)
{
for(int x=max(b/2,a/3)+1;x<=a/2;x++)
{
w[i]=x,w[cnt++]=a-x;
dfs();
//恢复现场
w[i]=a,cnt--;
}
}
else return;
}
}
int main()
{
cin>>n;
w[cnt++]=n;//初始化只有一堆,w[0]=n,cnt=1
dfs();//暴力枚举所有方案数
cout<<m<<" "<<d<<endl;
return 0;
}