2019/1/5考试题解

T1整数划分

题目描述

读入一个正整数n。要求将n写成若干个正整数之和,并且使这些正整数的乘积最大。例如,n=13,则当n表示为4+3+3+3(或2+2+3+3+3)时,乘积=108为最大。

输入

一个整数,n。

输出

第1行输出一个整数,为最大乘积的位数。第2行输出最大乘积的前100位,如果不足100位,则按实际位数输出最大乘积。(提示:在给定的范围内,最大乘积的位数不超过5000位)。

样例输入

13

样例输出

3
108

数据范围

10 ≤ n ≤ 31000

解题思路

首先确定一点,这道题是需要用高精的,=-=估计看得一些人头疼,毕竟高精这种东西有些难弄。一个还好,如果要写多个高精那简直令人崩溃。

我们首先就要确定方法。其实看着这道题我们可以想到一个非常简单的DP(没想到吧,这种题还能用DP)

dp[i] = max(dp[j] * dp[i -j])

够简单吧。。。但这里需要弄一下初值,也就是dp[2] = 2 , dp[3] = 3。为什么要这样弄呢?我们发现如果用其他的数凑成2,dp[2]的最大值只能是1,但这样还不如用它本身来的划算。

这一个DP有两个变量,以这样的数据范围,枚举直接爆了。考试的时候,我就用的这一个方法,我们就必须找更多的性质,于是,打一下表

i = 1 dp[i] = 1
i = 2 dp[i] = 2 (1)
i = 3 dp[i] = 3 (2)
i = 4 dp[i] = 4
i = 5 dp[i] = 6
i = 6 dp[i] = 9
i = 7

dp[i] = 12

i = 8 dp[i] = 18
i = 9 dp[i] = 27
i = 10 dp[i] = 54

看起来并没有什么规律。。。但我们看看,除了2,3,其余的dp值都比i的值大,然而看看其他的dp值,诶。。。好像正是2与3组合所成。。的确,那么我们仅仅需要将n分成由2与3组成的部分就可求出最优了。后面的所有dp值,本质上都是2与3所凑。从5开始,所有的dp值都比i大了。

那肯定2与3也要优先选一个先累计啊,我们又需要进行讨论,我们找比较特殊的点6.

可以知道6可以分成(2,2,2)或者(3,3)。很明显,后者更优。那么我们就可以知道,一个数如果分出来2的个数大于等于3,我们是可以将它们3个一对转换成2个3的。那么2的个数我们就控制在了2个以内。所以,这道题就是优先分3

一个特殊点:到了4,肯定分2个2,不是3与1。

那么这道题也仅仅需要高精乘,代码并不难。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int n , a[100005] , c[5005];
inline void cheng(int x){
    register int xx = 0 , i;
    for (i = 1;i <= c[0]  || xx;i ++){
        c[i] = c[i] * x + xx;
        xx = c[i] / 10;
        c[i] %= 10;
    }
    c[0] = i;
    while (c[c[0]] == 0 && c[0] > 1)
        c[0] --;
}
int main(){
    scanf("%d",&n);
    c[0] = c[1] = 1;
    register int tot = n / 3 ;
    register int tot1 = n % 3 , i;
    if (tot1 == 1){
        tot --;
        tot1 += 3;
    }
    for (i = 1;i <= tot ;i ++)
        cheng(3);
    if (tot1)
        cheng(tot1);    
    printf("%d\n",c[0]);
    for (i = c[0] ;i >= max (c[0] - 99 ,1) ; i --)
        printf("%d",c[i]);
}

总结

事实证明思路还是多去实现,这样就一步一步可能就可以找到更好的方法,打出正解。对于基本性质,要能看出来对于我来说还是有些困难。

T2地震

题目描述

农夫John的农场遭受了一场地震。有一些牛棚遭到了损坏,但幸运地,所有牛棚
间的路经都还能使用。FJ的农场有P个牛棚,编号1..P,  C条双向路经连接这些牛
棚,编号为1. . C。路经i连接牛棚ai和bi,路经可能连接ai到它自己,两个牛棚之
间可能有多条路经。农庄在编号为1的牛棚.,N头在不同牛棚的牛通过手机短信
reroortj告诉FJ它们的牛棚(reportj)没有损坏,但是它们无法通过路经和没有损坏
的牛棚回到到农场。当FJ接到所有短信之后,找出最小的不可能回到农庄的牛榭
数目。这个数目包括损坏的牛棚。

输入

第1行:三个空格分开的数:P, C,和N
第2 ...C+1行:每行两个空格分开的数:ai和bi
第C+2 ...C+N+1行:每行一个数:reportj

输出

第1行:一个数,最少不能回到农庄的牛的数目(包括损坏的牛棚)

样例输入

4 3 1
1 2
2 3
3 4
3

样例输出

3

解题思路

有些人可能不理解题意,其实可以这样理解,reportj就表示它不能够回到1,那么为啥它不能回到1呢?那么肯定它能够到1的某个必经之点是有问题的(要么破损,要么不能够回到1),由于我们要求的是最小值,我们尽量要让这个有问题的必经之点最优。

我们肯定令这一个点周围与它直接相连的点有问题即可。其实也很好看出来,如果说,你走了很远(中途经历了很多的点),这个时候你才令下一个必走的点断掉,那么前面走过的那些点都到不了1了。因为如果前面走过的点少了断掉的这一点还能到1,那么不能到1的那个点依旧可以到1,不合题意还要删除点。那岂不是很惨。违背了最优性,因此我们控制一下,尽量将范围锁定在打电话的这一个点的周围。于是,仅仅将与它直接相连的点删除标记即可。

本校大佬的图例(其实是题解,表示我不想画图)

暴力出奇迹,标记了点,dfs从1开始看哪些点能遍历到吧。最后统计就行了。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<iostream>
#include<vector>
#include<algorithm>
#define P 30005
using namespace std;
int p , c , n , ans;
int mov[P];
vector<int>G[P];
void dfs(int x){
    int xx = G[x].size();
    for (int i = 0; i < xx ; i ++){
        if (mov[G[x][i]] == 0){
            mov[G[x][i]] = 1;
            dfs(G[x][i]);
        }
    }
}
int main(){
    scanf ("%d%d%d",&p,&c,&n);
    for (int i = 1 ;i <= c ;i ++){
        int a,b ;
        scanf ("%d%d",&a,&b);
        G[a].push_back(b);
        G[b].push_back(a);
    }
    for (int i = 1 ;i <= n ;i ++){
        int j ;
        scanf ("%d",&j);
        mov[j] = -1;
        for (int k = 0 ;k <G[j].size(); k ++)
            mov[G[j][k]] = -1;
    }
    mov[1] = 1;
    dfs(1);
    for (int i = 2;i <= p ;i ++)
        if (mov[i] == -1 || mov[i] == 0)
            ans ++ ;
    printf("%d",ans);
}

总结

考试的时候成功没读懂题目,导致草草暴力过了样例。事后看看发现题意描述还是比较清晰的,只是因为我可能没有遇到过这种类型的题,因此思路无法转换,自然就做不出了。事实证明还是要多多做题,找到自己的问题。见识不同的题型,这样就可以使自己做题的时候可以不为题目而弄这么久了。

T3最长上升子序列

题目描述

给出一个长度为N的整数序列,求出包含它的第K个元素的最长上升子序列。

输入

第一行两个整数N, K
第二行N个整数

输出

如题目所说的序列长度。

样例输入

8 6
65 158 170 299 300 155 207 389

样例输出

4

数据范围

0 < N ≤ 200000,0 < K ≤ N

解题思路

这道题可以说解决的方式还是比较多的,最长上升子序列作为基础的DP想必各位都会。

朴素的DP算法是n^{2}的时间复杂度。做这道题,必须掌握进阶的算法。

的确,最长上升子序列是可以利用二分在O(n*log(n))的时间复杂度下完成。

欲知如何做,可以看看一道求最长不下降序列的题

知道了优化方法,这道题可以说是轻松很多了,很多方式都可以解决,这里列举一下

  1. 1到k求一个最长上升。n到k求一个最长下降,当放入k的时候,就得到了序列的长度。最后相加-1即可。
  2. 去掉1到k中小于a[k]的数,然后1到n求最长上升子序列。
  3. 从1到k-1中求一个最长上升,比a[k]大的数就不处理,从k + 1到n求一个最长上升,同样,比a[k]小的就不管。

主要的思路方法还是差不多。考的算法也很单一。

这里的二分可以用lower_bound来运算,但我不经常使用,这个函数是比较方便的。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<iostream>
#include<vector>
#include<algorithm>
#define N 200005
using namespace std;
int n , k , a[N] , dp[N] , len , ans;
int main(){
    scanf ("%d%d",&n,&k);
    for (int i = 1 ;i <= n;i ++){
        scanf("%d",&a[i]);
    } 
	for (int i = 1 ;i <= n ;i ++){
		if ((i < k && a[i] >= a[k]) || (i > k && a[i] <= a[k]))
			continue;
		if (a[i] > dp[len])
			dp[++ len] = a[i];
		else{	
			int j =lower_bound(dp + 1,dp + 1 + len,a[i]) - dp;
			dp[j] = a[i];
			len = max(len,j);
		}
	}
    printf("%d",len);
}

总结

考试的时候莫名错了,感到很奇怪,然而事后一打,一遍过,这就让我感到疑惑了,我考试的时候到底是哪里写得有问题呢?反正问题是有的,看起来又是很简单的细节问题,以后还是要注意一下,这种失分真的得不偿失啊。

考试总结

没有太大的问题,关键还是细节,这可以说是十分重要的。时间我觉得还是比较充裕的,打完代码后还是有一段不少的时间,然而代码的质量其实不算太高。发现考试打的代码都好复杂,事后再来做发现轻松多了。这便也说明了考试还真是考察综合能力,不仅仅只有编程,所以啊,只有经过多次的考试,才可能够在考场真正发挥自我的能力。

猜你喜欢

转载自blog.csdn.net/weixin_43904950/article/details/86145879