大家好我是秋刀鱼,今天给大家带来蓝桥杯题目题解。
一行两个整数N和M。
一行N个整数,表示N个礼物的重量。输出格式
一个整数,表示最小的最大重量。
样例输入
3 2
1 1 2样例输出
2
数据规模和约定
N, M <= 100,000
重量 <= 1,000
之前有写过:打包解析传送门
问题描述
我们用D(i)表示i有多少个约数。
例如 D(1)=1 D(2)=2 D(3)=2 D(4)=3。
给定n, 求D(1)+D(2)+D(3)+…+D(n)除以1000000007(10^9+7)的余数。输入格式
一个正整数n
输出格式
一行一个整数表示答案。
样例输入
4
样例输出
8
数据规模和约定
N<=10000000
如果使用常规解法是会超时的,数据量太大仅允许时间复杂度 O ( N ) O(N) O(N) 的算法。
可以换一种逻辑思考,既然已经知道了 n n n 的值,对于每一个数 i i i 能不能得到 1~n 中有多少个数拥有 i i i 这个约数呢?假设数量为 k k k ,结果值 a n s + = k ans+=k ans+=k。
既然有 k k k 这个约数,那一定是:k 2k 3k 4k 5k 6k ... nk
这些数能够拥有 k k k 余数,那么 1~n 中能整除 k k k 的数有多少呢?
答案是: n / i n/i n/i ,也就是说拥有 i i i 这个约数的值数量为: n / i n/i n/i ,因此遍历所有的 i i i 更新 ans 即使返回的答案
#include
#include
using namespace std;
const int mod = 1000000007;
int main() {
int n;
cin >> n;
long ans = n;
int sq = sqrt(n);
for (int i = 2; i <= n; ++i) {
ans += (n / i);
ans %= mod;
}
cout << ans;
return 0;
}
问题描述
有一条长为n的走廊,小明站在走廊的一端,每次可以跳过不超过p格,每格都有一个权值wi。
小明要从一端跳到另一端,不能回跳,正好跳t次,请问他跳过的方格的权值和最大是多少?输入格式
输入的第一行包含两个整数n, p, t,表示走廊的长度,小明每次跳跃的最长距离和小明跳的次数。
接下来n个整数,表示走廊每个位置的权值。输出格式
输出一个整数。表示小明跳过的方格的权值和的最大值。
样例输入
8 5 3
3 4 -1 -100 1 8 7 6样例输出
12
数据规模和约定
1<=n, p, t<=1000, -1000<=wi<=1000。
这道题核心思路是动态规划,定义状态 d p [ i ] [ j ] dp[i][j] dp[i][j]。
假设格的下标开始为1,那么 d p [ i ] [ j ] dp[i][j] dp[i][j] 存储"小明跳跃到第 i i i 格且最多还有 j j j 次跳跃机会" 状态下的跳过方格的最大权值。
状态转移方程
对于 d p [ i ] [ j ] dp[i][j] dp[i][j] 的状态。假设小明跳跃的最长距离为 p p p , 如果跳跃到 i i i 格,一定是从 [ i − p , i ] [i-p,i] [i−p,i] 中的一个格子跳跃,将之前跳跃的这个格子视为过去状态。那么 d p [ i ] [ j ] dp[i][j] dp[i][j] 一定是所有过去状态的最大值,也就是能一步跳跃到 i i i 点且剩余步数为 j + 1 j+1 j+1 时状态的最大值,即: d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j + 1 ] , d p [ i − 2 ] [ j + 1 ] . . . d p [ i − p ] [ j + 1 ] ) + a r r [ i ] dp[i][j]=max(dp[i-1][j+1],dp[i-2][j+1]...dp[i-p][j+1])+arr[i] dp[i][j]=max(dp[i−1][j+1],dp[i−2][j+1]...dp[i−p][j+1])+arr[i],也就是说当前的状态一定能通过之前的状态推导而来。
例如 p = 3 p=3 p=3 , d p [ 5 ] [ 3 ] dp[5][3] dp[5][3] = m a x ( d p [ 4 ] [ 4 ] , d p [ 3 ] [ 4 ] , d p [ 2 ] [ 4 ] ) + a r r [ 5 ] max(dp[4][4],dp[3][4],dp[2][4])+arr[5] max(dp[4][4],dp[3][4],dp[2][4])+arr[5]
状态初始化
为了开始时所有状态都设置为给定的一个极小值,同时将第一次跳跃能够到达的格子 i i i 预先处理,即: d p [ i ] [ t − 1 ] = a r r [ i ] dp[i][t-1] =arr[i] dp[i][t−1]=arr[i]
得到结果
最终得到的 d p dp dp 数组中 , d p [ n + 1 ] [ t ] dp[n+1][t] dp[n+1][t] 就是返回的结果
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define ll long long
#define M 1010
#define M_MIN -1000000
using namespace std;
int arr[M];
ll dp[M][M];
int main()
{
int n, p, t;
cin >> n >> p >> t;
for (int i = 1; i <= n; i++)
{
cin >> arr[i];
}
// 初始化
memset(dp, 0, sizeof(dp));
for (int i = 0; i < M; ++i) {
for (int j = 0; j < M; ++j) {
dp[i][j] = M_MIN;
}
}
// 处理一次跳跃的情况
for (int i = 1; i <= n + 1 && i <= p; i++)
{
dp[i][t - 1] = arr[i];
}
// 对于每一格
for (int i = 2; i <= n + 1; i++)
{
for (int j = 0; j <= t; j++)
{
// 寻找最大值,注意边界处理
for (int k = 1; k <= p && k < i; k++)
{
dp[i][j] = max(dp[i][j], dp[i - k][j + 1] + arr[i]);
}
}
}
cout << dp[n + 1][0];
return 0;
}
其实开始做这道题时我以为上面的代码可能会超时,因为时间复杂度接近 O ( n 3 ) O(n^3) O(n3) (然而并没有)。时间复杂度之所以这么高是因为在遍历 d p dp dp 中每一个元素过程中,每次的遍历都会带来至多 p p p 次的额外运算,目的是为了寻找最大值,但其实大部分的运算都是重复运算,浪费了很多时间。
还是拿这个例子举例:
红色部分对应的是 i = 5 , j = 3 i=5,j=3 i=5,j=3 时,需要搜索的元素。那如果是 i = 6 , j = 3 i=6,j=3 i=6,j=3 的情况呢?如下图所示:
如果我们能够实现一种数据结构,在实现队列添加、删除元素的基础上,能够在接近 O ( 1 ) O(1) O(1) 的时间复杂度下获取到队列中的最大元素,本题目就能够优化。
数据结构实现
为了实现这个数据结构,定义了一个队列 q u 1 qu1 qu1 存放所有元素,定义队列 q u 2 qu2 qu2 存放大小关系,保证先加入的元素如果小于后加入的元素,先加入的元素会被移除队列,且 q u 2. b a c k ( ) qu2.back() qu2.back() 存储 q u 1 qu1 qu1 队列中最大的元素。
struct maxQueue {
// 存储所有元素
queue<ll>qu1;
// 队列末尾存放最大元素
deque<ll>qu2;
// push 方法的实现
void update(int val) {
// 如果队列中元素个数超过p,移除最开始加入的元素
if (qu1.size() == p) {
this->pop();
}
// 添加元素
qu1.push(val);
while (!qu2.empty() && qu2.back() < val) {
qu2.pop_back();
}
qu2.push_back(val);
}
int max() {
return qu2.empty() ? M_MIN : qu2.front();
}
private:
void pop() {
if (qu2.front() == qu1.front()) {
qu2.pop_front();
}
qu1.pop();
}
// 每一列对应的数值队列
}maxStack[M];
逻辑实现
现在还是预先处理一步到达格子的状态,即 d p [ i ] [ t − 1 ] = a r r [ i ] dp[i][t-1]=arr[i] dp[i][t−1]=arr[i]
在遍历 d p dp dp 数组的第 i i i 行之前,首先将 i − 1 i-1 i−1 行的数组更新到队列中,随后开始遍历第 i i i 行更新状态,其余代码与前面的方法相似。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ll long long
using namespace std;
#define M 1010
#define M_MIN -1000000
int n, p, t;
int arr[M];
int dp[M][M];
struct maxQueue {
// 存储所有元素
queue<ll>qu1;
// 队列末尾存放最大元素
deque<ll>qu2;
// push 方法的实现
void update(int val) {
// 如果队列中元素个数超过p,移除最开始加入的元素
if (qu1.size() == p) {
this->pop();
}
// 添加元素
qu1.push(val);
while (!qu2.empty() && qu2.back() < val) {
qu2.pop_back();
}
qu2.push_back(val);
}
int max() {
return qu2.empty() ? M_MIN : qu2.front();
}
private:
void pop() {
if (qu2.front() == qu1.front()) {
qu2.pop_front();
}
qu1.pop();
}
}maxStack[M];
int main() {
memset(arr, 0, sizeof(M));
memset(dp, 0, sizeof(dp));
cin >> n >> p >> t;
// 初始化
for (int i = 0; i <= n; ++i) {
for (int j = 0; j <= n; ++j) {
dp[i][j] = M_MIN;
}
}
for (int i = 1; i <= n; ++i) {
cin >> arr[i];
}
for (int i = 1; i <= p && i <= n; ++i) {
dp[i][t - 1] = arr[i];
}
for (int i = 2; i <= n + 1; ++i) {
// 更新上一行的状态到队列中,注意对应位置
for (int j = 0; j <= t - 1; ++j) {
maxStack[j].update(dp[i - 1][j + 1]);
}
// 修改 i 行状态
for (int j = t - 2; j >= 0; --j) {
dp[i][j] = maxStack[j].max() + arr[i];
}
}
cout << dp[n + 1][0];
return 0;
}
上面是两种方法的耗时情况比较,其中第二行数据是未优化的代码,第一行的数据时优化后的代码。时间复杂度从 O ( n 3 ) O(n^3) O(n3) 降为接近 O ( n 2 ) O(n^2) O(n2) ,效果也是相当的明显,非常好用!
写在最后
代码、论述中有任何问题,欢迎大家指出,同时如果有任何疑问,也能够在评论区中留言,大家共同讨论共同进步!
如果觉得博主写的不错的话,可以点赞支持一下!