考试(1月5日)

整数划分

题目

读入一个正整数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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值