文章目录
A 试题 算法训练 结点选择
难度:高 题目类型:动态规划-树型动态规划
资源限制
时间限制:1.0s 内存限制:256.0MB
问题描述
有一棵 n 个节点的树,树上每个节点都有一个正整数权值。如果一个点被选择了,那么在树上和它相邻的点都不能被选择。求选出的点的权值和最大是多少?
输入格式
第一行包含一个整数 n 。
接下来的一行包含 n 个正整数,第 i 个正整数代表点 i 的权值。
接下来一共 n-1 行,每行描述树上的一条边。
输出格式
输出一个整数,代表选出的点的权值和的最大值。
样例输入
5
1 2 3 4 5
1 2
1 3
2 4
2 5
样例输出
12
样例说明
选择3、4、5号点,权值和为 3+4+5 = 12 。
数据规模与约定
对于20%的数据, n <= 20。
对于50%的数据, n <= 1000。
对于100%的数据, n <= 100000。
权值均为不超过1000的正整数。
解题思路:
树形动态规划
核心,即状态转移方程:
代码如下:
#include<bits/stdc++.h>
using namespace std;
int dp[100010][2];
vector<vector<int> >link;
//深度遍历,先深入到叶子结点,然后一层一层往上回升,一直到根结点,即第一个结点(初始pre为0是因为根结点没有父结点,这里用0表示)
void dfs(int x, int pre)
{
int temp;
for(int i = 0; i < link[x].size(); i++)
{
temp = link[x][i];//temp代表与x相连的子节点,x可以理解为父结点
if(temp != pre)//如果指向的子结点和父结点重合,则说明这个结点是叶子结点,不需要进一步dp
{
dfs(temp,x);
//深度遍历到最里面的叶子结点的父结点
dp[x][0] += max(dp[temp][0],dp[temp][1]);// 父结点(不选) += max(子结点(不选),子结点(选))
dp[x][1] += dp[temp][0]; // 父结点(选) += 子结点(不选)
}
}
}
int main(int argc, char** argv) {
//输入结点个数
int n;
cin>>n;
//输入各个结点的权值
for(int i = 1; i <= n; i++)
{
cin>>dp[i][1];
}
//输入n-1条边
link.resize(n+1);
int a,b;
for(int i = 1; i <= n-1; i++)
{
cin>>a>>b;
link[a].push_back(b);
link[b].push_back(a);
}
//深度优先遍历,从第一个结点开始遍历
dfs(1,0);
//输出权值和的最大值
cout<<max(dp[1][0],dp[1][1])<<endl;
return 0;
}
B 试题 算法训练 动态数组使用
难度:低 题目类型:数学-求和 求平均值
资源限制
时间限制:1.0s 内存限制:512.0MB
从键盘读入n个整数,使用动态数组存储所读入的整数,并计算它们的和与平均值分别输出。要求尽可能使用函数实现程序代码。平均值为小数的只保留其整数部分。
样例输入: 5 3 4 0 0 2 样例输出: 9 1
样例输入: 7 3 2 7 5 2 9 1 样例输出: 29 4
#include<bits/stdc++.h>
using namespace std;
int main(){
int *a,n;
cin>>n;
a=new int[n];
int sum=0;
for(int i=0; i<n; i++){
cin>>a[i];
sum+=a[i];
}
cout<<sum<<" "<<sum/n;
return 0;
}
C 试题 算法提高 身份证号码升级
难度:低 题目类型:基本算法-模拟
资源限制
时间限制:1.0s 内存限制:256.0MB
问题描述
从1999年10月1日开始,公民身份证号码由15位数字增至18位。(18位身份证号码简介)。升级方法为:
1、把15位身份证号码中的年份由2位(7,8位)改为四位。
2、最后添加一位验证码。验证码的计算方案:
将前 17 位分别乘以对应系数 (7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2) 并相加,然后除以 11 取余数,0-10 分别对应 1 0 x 9 8 7 6 5 4 3 2。
请编写一个程序,用户输入15位身份证号码,程序生成18位身份证号码。假设所有要升级的身份证的四位年份都是19××年
输入格式
一个15位的数字串,作为身份证号码
输出格式
一个18位的字符串,作为升级后的身份证号码
样例输入
110105491231002
样例输出
11010519491231002x
数据规模和约定
不用判断输入的15位字符串是否合理
解题思路:
根据题目意思代码模拟一下升级身份证的过程即可。
#include<bits/stdc++.h>
using namespace std;
int main()
{
char a[18];
string s;
while(cin>>s){
for(int i=0;i<8;i++)
a[i]=s[i];
a[6]='1';a[7]='9';
for(int i=8;i<17;i++) a[i]=s[i-2];
int sum=0;
int b[17]={7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2};
for(int i=0;i<17;i++){
sum+=int (a[i]-'0')*b[i];
}
char c[11]={'1','0','x','9','8','7','6','5','4','3','2'};
for(int i=0;i<11;i++){
if(sum%11==i){
a[17]=c[i];
break;
}
}
for(int i=0;i<18;i++)cout<<a[i];
cout<<endl;
}
return 0;
}
D 试题 算法提高 排列数
难度:中 题目类型:数学-排列组合 / DFS
资源限制
时间限制:1.0s 内存限制:256.0MB
问题描述
0、1、2三个数字的全排列有六种,按照字母序排列如下:
012、021、102、120、201、210
输入一个数n
求0~9十个数的全排列中的第n个(第1个为0123456789)。
输入格式
一行,包含一个整数n
输出格式
一行,包含一组10个数字的全排列
样例输入
1
样例输出
0123456789
数据规模和约定
0 < n <= 10!
解题思路:
第一种方法使用:C++内置的next_permutation函数,即可得到全排列。
第二种方法使用dfs去求排列数。
#include<bits/stdc++.h>
using namespace std;
int main(){
int num[10]={0,1,2,3,4,5,6,7,8,9};
//sort(num,num+10);因为这里输入的数组本来就是排序好的 所以这里不需要再排序一次了
long long n=0;
cin>>n;
do{
n--;
if(n==0){
for(int i=0;i<10;i++){
cout<<num[i];
}
}
}while(next_permutation(num,num+10) && n!=0);//next_permutation全排列
return 0;
}
E 试题 算法提高 P1001
难度:中 题目类型:基本算法-模拟 / 高精度乘法
资源限制
时间限制:1.0s 内存限制:256.0MB
当两个比较大的整数相乘时,可能会出现数据溢出的情形。为避免溢出,可以采用字符串的方法来实现两个大数之间的乘法。具体来说,首先以字符串的形式输入两个整数,每个整数的长度不会超过8位,然后把它们相乘的结果存储在另一个字符串当中(长度不会超过16位),最后把这个字符串打印出来。例如,假设用户输入为:62773417和12345678,则输出结果为:774980393241726.
输入:
62773417 12345678
输出:
774980393241726
解题思路:
方法一:因为这道题结果是16位不会超出long long 的范围可以直接longlong的两个数相乘即可。
方法二:是转换成字符串或者数组模拟乘法的过程。
方法三:还有一种方法是不转换成字符串或者数组的拆分数字做法,原理如下:
方法一代码:
#include <iostream>
using namespace std;
int main(int argc, char *argv[]) {
long long a,b;
cin>> a>> b;
cout<< a*b;
return 0;
}
方法二代码:
#include<bits/stdc++.h>
using namespace std;
const int N=10001;
int res[N*2], a[N], b[N];
string sa,sb;
int main() {
cin>>sa>>sb;
if(sa=="0"||sb=="0"){//没有这个的话最后一组用例无法通过
cout<<0;
return 0;
}
int lenA = 0, lenB = 0;
for(int i = sa.size()-1; i >= 0; i--)
a[lenA++] = sa[i]-'0';
for(int i = sb.size()-1; i >= 0; i--)
b[lenB++] = sb[i]-'0';
for(int i = 0; i < lenA; i++)
for(int j = 0; j < lenB; j++)
res[i+j] += a[i]*b[j];
for(int i = 0; i < lenA+lenB; i++){
if(res[i] >= 10){//进位
res[i+1] += res[i]/10;
res[i] %= 10;
}
}
int len = lenA+lenB-1;
if(res[len] != 0) cout<<res[len];
for(int i = len-1; i >= 0; i--) cout<<res[i];
return 0;
}
F 试题 历届试题 买不到的数目
难度:高 题目类型:数学-扩展欧几里得 / 动态规划
资源限制
时间限制:1.0s 内存限制:256.0MB
问题描述
小明开了一家糖果店。他别出心裁:把水果糖包成4颗一包和7颗一包的两种。糖果不能拆包卖。
小朋友来买糖的时候,他就用这两种包装来组合。当然有些糖果数目是无法组合出来的,比如要买 10 颗糖。
你可以用计算机测试一下,在这种包装情况下,最大不能买到的数量是17。大于17的任何数字都可以用4和7组合出来。
本题的要求就是在已知两个包装的数量时,求最大不能组合出的数字。
输入格式
两个正整数,表示每种包装中糖的颗数(都不多于1000)
输出格式
一个正整数,表示最大不能买到的糖数
样例输入1
4 7
样例输出1
17
样例输入2
3 5
样例输出2
7
解题思路:
第一种方法:扩展欧几里得
自然数a,b互质,则不能表示成ax+by(x,y为非负整数)的最大整数是ab-a-b.
而不互质的自然数,没有
证明:
a或者b是1的情况下容易证明.
以下情况都是a>1且b>1的情况.
首先证明ab-a-b不能表示成ax+by
假设ab-a-b=ax+by,那么ab=am+bn (m,n都大于等于1)
左边是a的倍数,右边am是a的倍数,那么要求bn也要是a的倍数
b不是a的倍数,只能要求n是a的倍数,这样的话,bn=bn’a>=ba
那么am=ab-bn<=0就与am>1矛盾.
第二种方法 动态规划
假如两个整数中最小的是min,最大的是max,那么我们知道一个数是否可以由输入的两个整数组成就看 当前数减去min或者减去max是否可以由输入的俩个数组成
设如果可以由输入的两个数组成那么dp[i]=1,否则为0。
那么就有***if(dp[i-max]==1||dp[i-min]==1) dp[i]=1***。
从这里我们就可以知道如果有连续的min个数可以由这两个数组成
那么后面的所有数都可以由其组成这是很显然的。
方法一代码:
#include<bits/stdc++.h>
using namespace std;
int main()
{
int a,b;
cin>>a>>b;
cout<<a*b-a-b<<endl;
return 0;
}
方法二代码:
#include<bits/stdc++.h>
using namespace std;
int main(){
int n,m;
cin>>n>>m;
int lmax=max(n,m);
int lmin=min(n,m);
int i=lmax+1;
int dp[100001]; //建立一个dp数组
memset(dp,0,sizeof(dp));
dp[lmax]=dp[lmin]=1; //初始化 dp[lmax] dp[lmin]
int ans=0; //记录可以由两个正整数组成的数的个数(连续的若中间出现了不满足的则重置为0)
while(i){
if(dp[i-lmax]==1||dp[i-lmin]==1) {// 如果有个一满足那就 dp[i]=1;
dp[i]=1;
ans++;
}
else {//如果中间 出现不满足的 那就 重置ans
ans=0;
}
if(ans==lmin)break;//如果ans==lmin 那i后面的所有数都可以由俩个数组成
i++;
}
int l=0;
for(int j=0;j<=i;j++){
if(dp[j]==0){
l=j;//寻找最大的买不到的数目
};
}
cout<<l<<endl;
return 0;
}