整数划分
题目
读入一个正整数n。要求将n写成若干个正整数之和,并且使这些正整数的乘积最大。例如,n=13,则当n表示为4+3+3+3(或2+2+3+3+3)时,乘积=108为最大。
输入
n
输出
第1行输出一个整数,为最大乘积的位数
。第2行输出最大乘积的前100位
,如果不足100位,则按实际位数
输出最大乘积。(提示:在给定的范围内,最大乘积的位数不超过5000位
)。(10 <= n <= 31000)
思路
如何分解这个n,才能使乘积最大,这是此题的核心。
首先明白一个定理:两正数相差越小,其乘积就越大
证明过程如下
设正数n,y,x(n>y>x),比较n*(n-x)与n*(n-y)
我们用作差法,即用 n*(n-x)-n*(n-y)
⇒ n-x-n+y
⇒ y-x
∵ y>x
∴ n(n-x)>n(n-y) ( x < y )
看到这里,有些人就觉得直接 n/2*(n-n/2) 就行了,既然你都分了n那你为什么不再把n/2再分呢?
是的,这样下去会分到n个1,乘积又变小了,所以我们就把n分成全部都是二,但n为奇数的时候会多出来一个1,其实这时就可以把1与其中一个2合并,成为3,毕竟我们知道 3 * x > 2 * x * 1 ( x > 0 )。
但仔细推敲就会发现一个例外: 6
6 = 3+3 = 2+2+2,但是 3 * 3 > 2 * 2 * 2, 所以,当 n >= 6 时,(n-6) * 3 * 3 >= (n-6) * 2 * 2
同样的,若(n-6)中还有6,说明还可以分。
我们再看,n<6的时候,按照前面一个定理就可以了,但你还是会发现它们也遵循着先分3的原则,只不过在n%3==1的时候,拿出了一个3与1组成了两个2,因为我们知道 x * 3 * 1 < x * 2 * 2(x 为正整数)
所以总结一下吧
先分三,再分二,不分一;有一有三取成两个二
所以,此题就变成了一道水题,qkp和高精就行了
代码
#include <cstdio>
#include <cctype>
#include <iostream>
#include <algorithm>
using namespace std;
#define M 5005
int sum, n, len, len_3, ss;
int he[M], yin[M], ans[M];
void Rd(int &x){
x = 0;
char c = getchar();
while( !isdigit(c) ) { c = getchar(); }
while( isdigit(c) ){
x = (x << 3)+(x << 1)+(c ^ 48);
c = getchar();
}
}
void qkp(int y){
while( y ){
if( y&1 ){
int linshi[M]={};
for(int i = 1; i <= len; i ++){
for(int j = 1; j <= len_3; j ++){
linshi[i+j-1] += he[i]*yin[j];
linshi[i+j] += linshi[i+j-1]/10;
linshi[i+j-1] %=10;
}
}
len += len_3;
while( !linshi[len] && len > 0 ) len --;
for(int i = 1; i <= len; i ++)
he[i] = linshi[i];
}
y /= 2;
int linshi[M*4]={};
for(int i = 1; i <= len_3; i ++){
for(int j = 1; j <= len_3; j ++){
linshi[i+j-1] += yin[i]*yin[j];
linshi[i+j] += linshi[i+j-1]/10;
linshi[i+j-1] %= 10;
}
}
len_3 *= 2;
len_3++;
while( !linshi[len_3] && len_3 > 0 ) len_3 --;
for(int i = 1; i <= len_3; i ++)
yin[i] = linshi[i];
}
}
int main(){
Rd(n);
ss = 1;
he[1] = 1;
yin[1] = 3;
len = len_3 = 1;
if( n%3 == 2 ){
sum = n/3;
ss = 2;
}
else if( n%3 == 1 ){
sum = n/3-1;
ss = 4;
}
else
sum = n/3;
qkp(sum);
for(int i = 1; i <= len; i ++){
ans[i] += he[i]*ss;
ans[i+1] += ans[i]/10;
ans[i] %= 10;
}
if( ans[len+1] ) len++;
printf("%d\n", len);
for(int i = len; i >= max(1,len-99); i --)
printf("%d", ans[i]);
return 0;
}
最长上升子序列(不一样)
题目
给出一个长度为N的整数序列,求出包含它的第K个元素的最长上升子序列。
输入
第一行n与k
第二行n个元素
输出
如题目的所说的子序列的长度(0 < k <= n < 200000)
思路
此题肯定不一般呀,朴素一定爆啊,所以没学过最长上升子序列优化方法的同学先补下课咩——点这里哦
我们分两段,1-k 和 k-n
1-k这段我们只要找到ak最后代替后所在的位置就行了
而如何搞k-n这一段呢?
如果我们单纯从k-n,万一这一段中有比ak小的,那么这样顺着来的LIS就可能不会含有ak,就会WA。
所以我们倒着来,这样就和1-k这一段的方法一样,那么就会发现一个问题,这样倒着来是要搞一个下降啊,那么如何把大的变小,小的反而变大呢,其实很简单,把它变为它的相反数,这样我们就可以从n-k的顺序来求n-k这一段的下降子序列,即为k-n的上升子序列,因为算了两次ak,所以需要减1
代码
#include <cstdio>
#include <cctype>
#include <iostream>
#include <algorithm>
using namespace std;
#define M 200005
#define INF 0x3f3f3f3f
int n, k, len, len1;
int a[M], dp[M], dp_[M];
void Rd(int &x){
x = 0;
int f = 1; char c = getchar();
while( !isdigit(c) ) { if(c == '-') f = -1; c = getchar(); }
while( isdigit(c) ) { x = (x << 3)+(x << 1)+(c ^ 48); c = getchar(); }
x *= f;
}
int main(){
Rd(n), Rd(k);
for(int i = 1; i <= n; i ++){
Rd(a[i]);
dp[i] = INF;
dp_[i] = INF;
}
for(int i = 1; i <= k; i ++){
int p = lower_bound(dp+1, dp+1+n, a[i])-dp;
if( k == i )
len = p;
dp[p] = a[i];
}
for(int i = k; i <= n; i ++)
a[i] *= -1;
for(int i = n; i >= k; i --){
int p = lower_bound(dp_+1, dp_+1+n, a[i])-dp_;
if( k == i )
len1 = p;
dp_[p] = a[i];
}
printf("%d\n", len+len1-1);
return 0;
}