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[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
",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算法是的时间复杂度。做这道题,必须掌握进阶的算法。
的确,最长上升子序列是可以利用二分在的时间复杂度下完成。
欲知如何做,可以看看一道求最长不下降序列的题。
知道了优化方法,这道题可以说是轻松很多了,很多方式都可以解决,这里列举一下
- 1到k求一个最长上升。n到k求一个最长下降,当放入k的时候,就得到了序列的长度。最后相加-1即可。
- 去掉1到k中小于a[k]的数,然后1到n求最长上升子序列。
- 从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);
}
总结
考试的时候莫名错了,感到很奇怪,然而事后一打,一遍过,这就让我感到疑惑了,我考试的时候到底是哪里写得有问题呢?反正问题是有的,看起来又是很简单的细节问题,以后还是要注意一下,这种失分真的得不偿失啊。
考试总结
没有太大的问题,关键还是细节,这可以说是十分重要的。时间我觉得还是比较充裕的,打完代码后还是有一段不少的时间,然而代码的质量其实不算太高。发现考试打的代码都好复杂,事后再来做发现轻松多了。这便也说明了考试还真是考察综合能力,不仅仅只有编程,所以啊,只有经过多次的考试,才可能够在考场真正发挥自我的能力。