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

 

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

You are to write a program that will determine how many delegates should contain each group in order for Parliament to work as long as possible.

会议维持的时间就是这些 disjoint groups size 的乘积,这个题目的意思实际上说的就是一个整数拆分问题。即,把 N 分为各不相同的加数的和,使这些加数的乘积最大。该题输入的 N [0,1000] 范围内。
看到这个题目是整数拆分时,有以下几个思路:动态规划;生成函数;枚举;分析题目,总结规律。
枚举显然不现实,从 JOJ 1026 就可以知道,当 N 100 时它的分部量各不相同的分拆方法数已经是一个天文数字了。生成函数是因为它解决过 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 替换为 2 3 ,则可以得到更大的乘积 ( 因为 2*3 > 5) ,同理,对于 x>5 的,从 x 中分离出一个加数 2, 可以得到 2*(x-2) = 2*x – 4 > x ,或者分离出一个加数 3, 3*(x-3) = 3 * x – 9 > x ,即总能把大的加数分解为小的加数得到更大乘积。进一步总结规律可以发现,要想让乘积最大,必须 3 的个数最多,所以,首先把这个数分为 k 3 的和,若刚好分完则最大乘积为 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
然后分情况讨论:

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

1
。若剩余值( n-sum )等于 w ,则最后输出序列为: 3 4 …… w w+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;
}
//*/

你可能感兴趣的:(网络,each,delegates)