JOJ 1124 Parliament 最大乘积的整数分拆

来源:互联网 发布:网络支持中心网站域名 编辑:程序博客网 时间:2024/06/15 17:20

 

题目链接:http://acm.jlu.edu.cn/joj/showproblem.php?pid=1124&off=1100
原题:
New convocation of The Fool Land's Parliamentconsists of N delegates. According to the present regulation delegatesshould be divided into disjoint groups of different sizes and every dayeach group has to send one delegate to the conciliatory committee. Thecomposition of the conciliatory committee should be different each day.The Parliament works only while this can be accomplished.

Youare to write a program that will determine how many delegates shouldcontain each group in order for Parliament to work as long as possible.

会议维持的时间就是这些disjoint groupssize的乘积,这个题目的意思实际上说的就是一个整数拆分问题。即,把N分为各不相同的加数的和,使这些加数的乘积最大。该题输入的N[0,1000]范围内。
看到这个题目是整数拆分时,有以下几个思路:动态规划;生成函数;枚举;分析题目,总结规律。
枚举显然不现实,从JOJ 1026就可以知道,当N100时它的分部量各不相同的分拆方法数已经是一个天文数字了。生成函数是因为它解决过JOJ 1026,但这个思路应该不适用于这个题目,因为这个题目要的不是分拆方法数,而是有可能会去考察每一个具体的分拆。这样的情况下,只有动态规划和数学方法能解决。
动态规划的思路很容易得到,完全套用自己解决JOJ 1026时的DP思路就可以(这个DP思路不是最好的,但因为自己做过一次对它最熟悉,以后若有时间再考虑另外的DP方式),令f(n,k)表示将n分拆为最小分部量为k的所有分拆方法中的最大乘积,则有递推公式:
f(n,k) = max{f(n-k, x) } * k
其中,k+1 <= x <= n-k,边界条件:f(n,n) = n, f(n, m) = 0 m > n
但同样要注意,这个乘积是无比巨大的,如果不要求分部量互不相同,利用反证法可以知道1000的分拆中最大乘积为3^332*2,这是远超过double变量的表示范围的,所以,想另一种办法,我们可以把这个值进行放缩,可以对它取对数。这样,3^332 * 2取对数后的值不超过2000
另一个问题是得到最大乘积后如何得到分拆,应该有很好的方法,我使用的最笨的方法,另外用一个数组s[n][k]表示f(n,k)取最大值时的下一个分拆量,即f(n,k)的最小分拆量肯定是k了,除了k以外的最小的分拆量就是s[n][k]。即:
N = k + s[n][k] + ….
这是最大乘积分拆的方案,输出时:先枚举确定k的值,然后可以递归输出。使用动态规划的代码如下(这个代码是memory limit exceeded了,AC的代码使用的是数学的分析方法,见最后)
//* Memory Limit Exceeded
#include <cstdio>
#include <cmath>
 
double F[1001][1001];
short S[1001][1001];
 
double f(int N, int K) {
     if(K > N) return 0.0;
     if(F[N][K]) return F[N][K];
 
     double m = 0.0;
     for(int i = K + 1; i <= N-K; i++) {
         double t = f(N-K, i);
         if(t > m) {
              m = t;
              S[N][K] = i;
         }
     }
     return F[N][K] = m + log((double)K);
}
 
int main() {
     int n, scenario = 0;
    
     for(int i = 1; i <= 1000; i++) {
         F[i][i] = log((double)i);
     }
     while(scanf("%d", &n), n) {
         printf("Scenario #%d:/n", ++scenario);
 
         double m = 0.0;
         int mini = -1;
         for(int i = 1; i <= n; i++) {
              double t = f(n, i);
              if(t > m) {
                   m = t;
                   mini = i;
              }
         }
         printf("%d", mini);
         while(S[n][mini]) {
              printf(" %d", S[n][mini]);
              int t = S[n][mini];
              n = n - mini;
              mini = t;
         }
         printf("/n/n");
     }
     return 0;
}
 
//*/
现在介绍分拆的规律:先假设不要求分拆的分部量各不相同,求最大乘积?这可以考察它的反面来得到,即考察什么样的分拆得到的必定不是最大乘积?
可以看到,设分拆后的最大加数为x,若x=4,则把x替换为两个2,效果一样,若x=5,则把x替换为23,则可以得到更大的乘积(因为2*3 > 5),同理,对于x>5的,从x中分离出一个加数2,可以得到2*(x-2) = 2*x – 4 > x,或者分离出一个加数3,3*(x-3) = 3 * x – 9 > x,即总能把大的加数分解为小的加数得到更大乘积。进一步总结规律可以发现,要想让乘积最大,必须3的个数最多,所以,首先把这个数分为k3的和,若刚好分完则最大乘积为3^k,若余1则最大乘积就是3^(k-1)*4,若余2最大乘积就是3^k*2
要求所有分部量各不相同时这个分析方法同样有效,以下规律及证明来自于网络:
http://www.stubc.com/thread-2393-1-1.html
1.1<a1

if a1=1, then a1(=1), a[t] together could be replaced by a[t]+1.
reason:  a[t]+1>a[t]*1

----------------------------------------------------------------------------------------
2.to all i, 1<=a[i+1]-a<=2;

if some i make a[i+1]-a>2,
then a,a[i+1] together could be replaced by a+1,a[i+1]-1 together.

reason: a*a[i+1] < (a+1)*(a[i+1]-1)
(a+1)*(a[i+1]-1)=a*a[i+1]+a[i+1]-a-1
so a[i+1]-a-1>0   (* a[i+1]-a>2)

----------------------------------------------------------------------------------------
3. at MOST one i, fits a[i+1]-a=2

if i<j and a[i+1]-a=2 and a[j+1]-a[j]=2 then
a,a[j+1] could be replaced by a+1, a[j+1]-1

reason: a*a[j+1]< (a+1)*(a[j+1]-1)
so a[j+1]-a-1>0   (* a[j]-a>=1 a[j+1]-a[j]>=1 so a[j+1]-a>=2 )

----------------------------------------------------------------------------------------
4. a1<=3

if a1>=4, then a1,a2 together could be replaced by 2, a1-1, a2-1 together

reason: a1*a2< 2*(a1-1)(a2-1)
(a1-1)(a2-1)=a1*a2-a1-a2+1
so a1*a2>2*(a1+a2-1) (* a1>=4 and a2>=5)

----------------------------------------------------------------------------------------
5. if a1=3 and one i fits a[i+1]-a=2 then i must be t-1

if i<t-1 then a[i+2] could be replaced by 2, a[i+2]-2 together
reason: a[i+2]<2*(a[i+2])-4
so a[i+2]>4  (* a[1]=3 a[3]>=5 so a[i+2]>=5)

做法就是求出以2起始的最大连续自然数序列之和sum,使得sum的值不超过输入数n
然后分情况讨论:

设此最大序列为23……w,则:

1
。若剩余值(n-sum)等于w,则最后输出序列为:34……ww+2,即将原最大序列每项加1,再将最后剩余的一个1加到最后一项上。

2
。若剩余值(n-sum)小于w,则从序列的最大项i开始,从大到小依次将每项加1,直到剩余值用完。
以下是使用这种方法AC的代码:
//* Accepted
#include <cstdio>
 
int main() {
     int n, scenario = 0;
 
     //freopen("out1.txt", "w", stdout);
     while(scanf("%d", &n), n) {
         printf("Scenario #%d:/n", ++scenario);
 
         int ans[50], r = n, k = 2, i;
         for(i = 0; k <= r; i++, k++) {
              ans[i] = k;
              r -= k;
         }
         for(int j = i - 1; r; j--, r--) {
              ans[j]++;
              if(j == 0) j = i;
         }
         printf("%d", ans[0]);
         for(int j = 1; j < i; j++) printf(" %d", ans[j]);
         printf("/n/n");
     }
     return 0;
}
//*/
原创粉丝点击