目录
🌼一,1812: [NewOJ Week 5] 排列变换
🌼二,1827: [NewOJ Week 8] 升降数字
🌼三,剑指offer 10-II 青蛙跳台阶问题
🌼四,P1223 排队接水
🌼五,P5650 基础字符串练习题
🌼六, 1143: 挤牛奶
🌼七, 1148: 混合牛奶
🌼八,2035: [蓝桥杯2022初赛] X进制减法
🌼九, 1125: 打地鼠 未完成
🌼十, 1161: 三值排序
🌼十一,P1614 爱与愁的心痛
🌼十二,P2240 【深基12.例1】部分背包问题
题目,解析,代码,输入输出以及题目来源一一列出,需要的同学还可到题目来源自己试试,看能否Accepted,题目难度不分先后
复习时请点击题目来源,自己做一遍,不会的可以在评论区讨论
困难:一,五,六,八,九,十,十二(第一次35min内搞不定)
简单:二,三,四,七,十一
贪心算法一般适用于,需要高效占用某一公共资源的活动,比如
它实际上不是真正的算法,而是一种思路,这就需要我们在草稿纸上列出清楚的思路,
通过局部最优解推出全局最优解,不确定能否借局部得到全局时,可用反证法
如果举出反例,则不能仅使用贪心,而要靠动态规划等其他算法
贪心算法比较基础的有3个类型,分数背包,数字拼接,活动选择
🌼一,1812: [NewOJ Week 5] 排列变换
题目
给定一个长度为n的排列a,需要将这个排列变成b
每次可以选择一个数字往左移若干个位置
请求出最小移动次数
输入格式
第一行为正整数n,1≤n≤100000
第二行为n个整数,表示排列a
第三行为n个整数,表示排列b
输出格式
输出一个数字表示答案
输入
5
5 1 3 2 4
4 5 2 1 3
输出
2
类型:思维题,贪心 ,基础题
解析
1,
首先,我们需要两个数组保存两个排列
然后通过数组 seat[] 将b[i]中元素与下标对应起来,即seat[b[i]] = i
接着数组a中元素作为seat[]下标,得到a[i] 在 b 中下标,存到数组c[]中,即 c[i] = seat[a[i]] 也就是得到了原排列的元素在b排列中的位置
比如 a = {9, 4, 7, 8, 6, 1}, b = {7, 8, 1, 4, 9, 6}, seat = {3, 0, 0, 4, 0, 6, 1, 2, 5}
最后c = {5, 4, 1, 2, 6, 3},看草稿
关键:原题目通过seat[]数组和c[]数组,转化为,求通过几次左移,使 5, 4, 1, 2, 6, 3 变成 1, 2, 3, 4, 5, 6
如果直接两次for循环,从5开始,看4,1,2,6,3有无比5小的,再从4开始,看1,2,6,3有无比4小的,再。。。时间会超限,所以
2,
我们采取类似逆序数的思路,比如3,2,5,1,4中,3左边比它大的数没有,2有,5没有,1有,4有,共3个有,所以需要移动3次
3,
注意!代码第21行 seat[b[i]] = i 和第25行 c[i] = seat[a[i]]要分别放在两个for循环中,否则seat[]数组数据未输入完,c[i] = ....会出现等于0的情况,造成输出错误
代码1
#include<iostream>
#include<cstring> //memset()
#include<algorithm> //max()
using namespace std;
int a[100010], b[100010];
int c[100010], seat[100010];
//数组seat保存数组b元素对应的下标
//数组c保存数组a元素在b中的下标
int main()
{
memset(a, 0, sizeof(a)); //初始化数组
memset(b, 0, sizeof(b));
int n;
cin>>n;
for(int i = 1; i <= n; ++i) //下标从1开始
cin>>a[i];
for(int i = 1; i <= n; ++i) {
cin>>b[i];
seat[b[i]] = i; //b[i]为元素, i为下标
}
for(int i = 1; i <= n; ++i) {
c[i] = seat[a[i]]; //seat[a[i]]为a中元素在b的下标
} //由此转化为求数组c变成正序的需要移动的次数
int ans = 0; //记录左移最小次数
int Max = 0; //保留当前最大值
for(int i = 1; i <= n; ++i) {
if(c[i] < Max) ans++;
Max = max(Max, c[i]);
}
cout<<ans<<endl;
return 0;
}
输入输出
6
9 4 7 8 6 1
7 8 1 4 9 6
4
5
5 1 3 2 4
4 5 2 1 3
2
9
19 16 13 22 37 12 11 10 -5
-5 10 11 19 16 13 12 37 22
5
题目来源
P1812 - [NewOJ Week 5] 排列变换 - New Online Judge (ecustacm.cn)
🌼二,1827: [NewOJ Week 8] 升降数字
题目
升降数字:一个数字可以被分成两部分(可能有一部分是空的),前半部分属于非递减,后半部分属于非递增
例如12321、12345、54321、12333、32111都是升降数字
现在给定数字n,请求出不超过n的最大的升降数字
输入格式
输入第一行为正整数T,表示存在T组测试数据,1≤T≤100000
每组测试数据输入一行,包含一个数字n,1≤n<10^100000
输入保证所有数据长度之和不超过100000
输出格式
对于每组测试数据输出一个数字表示答案
输入
5
29041
56577
12345
54300
135341
输出
29000
56555
12345
54300
135333
分类:贪心 ,基础题
解析
1,
每个数字位数达10^100000,所以必须采用高精度的思路,将数字按字符串的格式输入,转化为整型后存入数组
2,
跟据贪心算法,先分类讨论,在草稿纸上对照样例进行模拟
然后得到这样的理解:上升后下降的最后一个位置,使其后续所有数字和它一样
3,步骤
(1)
先将输入的字符串,转化为整型存入数组,下面博客第一题有详细解释
C++高精度加法以及2023蓝桥杯备赛计划_码龄11天的博客-CSDN博客
(2)
保存数组中,数字上升前最后一个下降的位置
(3)
使这个位置后的数字,和这个位置都一样
(4)
逆序输出数组
代码
#include<iostream>
#include<cstring> //memset()
using namespace std;
int a[100010];
int main()
{
memset(a, 0, sizeof(a)); //初始化数组全部元素为0
int n;
cin>>n; //n组测试数据
while(n) {
string s; //将正整数按字符串输入
cin>>s;
for(int i= 0; i < s.size(); ++i)
a[i] = s[s.size() - 1 - i] - '0'; //从字符串最后一位开始存入数组首位
//bug1: 我第一次漏了- '0',字符串转ASCII常漏,比如转小写字母需要- 'a'
int seat = 0, flag = 0; //保存上升前最后一个下降的位置
for(int i = s.size() - 1; i >= 0; --i) { //逆序,即从大到小开始比较
if(a[i - 1] < a[i]) {
seat = i - 1;
flag = 1;
}
if(flag == 1 && a[i - 1] > a[i]) //出现下降后又上升
break;
}
for(int i = seat; i >= 0; --i)
a[i] = a[seat]; //上升后又下降的最后一个元素,之后的都一样
for(int i = s.size() - 1; i >= 0; --i) //逆序输出
cout<<a[i];
cout<<endl;
n--;
}
return 0;
}
输入输出
8
29041
29000
56577
56555
12345
12345
54300
54300
135341
135333
99878
99877
132100
132100
531201
531111
题目来源
P1827 - [NewOJ Week 8] 升降数字 - New Online Judge (ecustacm.cn)
🌼三,剑指offer 10-II 青蛙跳台阶问题
题目
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1
示例 1:
输入:n = 2
输出:2
示例 2:
输入:n = 7
输出:21
示例 3:
输入:n = 0
输出:1
提示:
0 <= n <= 100
类型:贪心,简单
解析
1,
首先明确,每次只能跳1级或2级,比如7级台阶,青蛙最后一步必定从5跳2级或者从6跳1级,从5级跳2级是一种可能,从6级跳1级也是一种可能,
容易发现,跳n级台阶的跳法就等于跳n - 1级台阶 + 跳n - 2级台阶的跳法,类似斐波那契数列1,1,2,3,5,8,13......而本题n = 0算1种跳法,n = 1有1种跳法,n = 2有2种跳法,后续的叠加就好
2,
答案取模10^9 + 7,没超过int范围,可以用int;
同时,代码第15行,边加边取余,结果不受影响,不信可以自己试试,比如1,2,3,5,8,13,21,34......
假设答案取余9,13%9 == 4,21%9 == 3,3 + 4 == 7,而(13 + 21) % 9 == 7,所以没有影响后续取余结果
3,
由于是力扣的题,没用过的可能不知道,力扣的题都是核心代码模式,和我们平时在本地编译器不一样,就是不需要写库函数头文件这些,只要在它给的框架内,把代码逻辑写出来,再return就好
4,
特别注意!!!力扣不需要处理输入输出,这道题我因为多了个cin>>n;代码通过了1/57测试用例,搞了3小时才搞明白,真坑,而且力扣最好不要声明全局变量。而且要是声明了局部静态变量,也一定要初始化,不然会有各种奇怪问题
代码
1,本地编译器代码
#include<iostream>
#include<cstring> //memset()
using namespace std;
int main()
{
int a[110];
memset(a, 0, sizeof(a));
int n;
cin>>n;
a[0] = 1;
a[1] = 1;
a[2] = 2;
for(int i = 3; i <= n; ++i) {
a[i] = (a[i - 1] + a[i - 2]) % 1000000007;
}
cout<<a[n]<<endl;
return 0;
}
由于力扣开头给了你n,所以在力扣提交代码前,把int n和cin>>n去掉
2,力扣代码
class Solution {
public:
int numWays(int n) {
int a[110];
memset(a, 0, sizeof(a));
a[0] = 1;
a[1] = 1;
a[2] = 2;
//cin>>n; 就这个cin害我搞了3小时答案错误
for(int i = 3; i <= n; ++i) {
a[i] = (a[i - 1] + a[i - 2]) % 1000000007;
}
return a[n];
}
};
输入输出
7
21
10
89
45
836311896
100
782204094
题目来源
剑指 Offer 10- II. 青蛙跳台阶问题 - 力扣(LeetCode)
🌼四,P1223 排队接水
题目
有 n 个人在一个水龙头前排队接水,假如每个人接水的时间为 Ti,请编程找出这 n 个人排队的一种顺序,使得 n 个人的平均等待时间最小
输入格式
第一行为一个整数 n
第二行 n 个整数,第 i 个整数 Ti 表示第 i 个人的等待时间 Ti
输出格式
输出文件有两行,第一行为一种平均时间最短的排队顺序;第二行为这种排列方案下的平均等待时间(输出结果精确到小数点后两位)
输入
10 56 12 1 99 1000 234 33 55 99 812
输出
3 2 7 8 1 4 9 6 10 5 291.90
n <= 1000, Ti <= 10^6,不保证 Ti 不重复
当 Ti 重复时,按照输入顺序即可(sort 是可以的)
类型:贪心,排序,普及-
解析
显然,要求平均等待时间最小,那么总的时间要最小,打水时间越长的人排越后面,这样别人就不用等那么久
要注意的点
1,由于可能存在等待时间相同的情况,此时输出的排队顺序存在不确定性,为了保证每个人的位置与时间一一对应,使用结构体
2,使用结构体还可以避免用两个数组得到排序后对应的序列(对于本题会复杂很多)(两数组做法参考第一题排列变换)
需要注意的是,第一题不能用结构体,因为是由题目输入排序不同的两个数组,而非本题对一个数组进行排序
3,最后计算总的排队时间,要用double,否则会溢出,双精度double可达10^308,而单精度float只能到10^38,本题如果n = 1000,每个数据都接近10^6,就会超过float范围,float只能Accepted54%
4,sort(起始地址,结束地址,比较器),比如 sort(a, a + n, cmp)
代码
#include<iostream>
#include<algorithm> //sort()
#include<iomanip> //setprecision(), setiosflags(ios::fixed)
using namespace std;
struct people //结构体可以将多个数据对应起来
{
int time, num;
};
bool cmp(people x, people y)
{
return x.time < y.time;
}//sort对结构体排序最好声明个比较函数
int main()
{
int n;
cin>>n;
struct people a[1010];
for(int i = 1; i <= n; ++i) {
cin>>a[i].time; //保存时间
a[i].num = i; //保存下标
}
sort(a + 1, a + 1 + n, cmp); //a的话起始地址默认a[0],而我们从下标1开始,所以a + 1
for(int i = 1; i <= n; ++i) cout<<a[i].num<<" ";
cout<<endl;//输出时间最短的排队顺序
//sum声明为float在洛谷Accepted只有54%,改成double才100%
double sum = 0.0, seat = n - 1; //seat保存处于等待的人数
for(int i = 1; i <= n - 1; ++i) {
sum += seat * a[i].time;
seat--; //至于为什么 i<=n-1可以举个例子在草稿纸试试
} //保留两位小数输出
cout<<setprecision(2)<<setiosflags(ios::fixed)<<sum / n<<endl;
return 0;
}
输入输出
12
8147 12 34 483942 111 99999 12 259 325 1 0 1024
11 10 2 7 3 5 8 9 12 1 6 4
10256.50
题目来源
P1223 排队接水 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
🌼五,P5650 基础字符串练习题
下面为大家展示2种代码
虽然思路一样,但第一种Accepted20%,不可取,代码不但长还臭,第二种是在第一种基础上的简化,同时Accepted 100%
题目
给定长度非零的非空 01 串 S
找出 S 的非空连续子串 T 满足串中 0 的个数减去 1 的个数最大
你只需要输出最大值即可
输入格式
一行一个 01 串表示 S
输出格式
一行一个数表示答案
输入
0111100101
输出
2
类型:贪心,普及-
解析
两个代码思路一样,区别是第二个代码思路清晰,第一个代码用到了dp(动态规划)中的栈,实际也没学过,不经意间用了但是细节没考虑全
思路:
1,换个角度思考,把0/1串想象成-1/1串,就转化成求非空子串的最大值
2,所以遍历数组,每次遍历都执行一次以下步骤:(实际就是代码2那4行代码)
(1)每次遇到0,0的个数加1(0的个数表示当前连续子串0个数 - 1个数的最大值)
(2)每次遇到1,0的个数减1
(3)当连续的1比前面连续的0多,不满足最大值的情况时,0的个数赋值为0
(4)若0的个数大于当前最大值,最大值 = 当前0的个数
3,坑:最后还考虑到如果全为1,因为非空,只要有一个0存在,最大值至少为1,若全为1,则0的个数为0,1的个数至少为1, 0 - 1 = -1,输出-1
代码1 (×) Accepted 20%
#include<iostream>
using namespace std;
int a[100010]; //|S|表示字符串S的长度
int b[100010]; //保存sum可能的最大值
int main()
{
string s;
cin>>s;
for(int i = 0; i < s.size(); ++i)
a[i] = s[i] - '0'; //字符转整型存入数组
int sum = 0;
int zero = 0, one = 0; //保存连续出现的0和1
int flag = 0; //做标记,判断先遇到连续0
int m = 0; //数组b下标,数组b用以保存可能的sum
//核心代码↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
for(int i = 0; i < s.size(); ++i) {
if(a[i] == 0) { //遇到0
one = 0;
zero += 1;
flag = 1;
}
if(a[i] == 1 && flag == 1) {//连续0后遇到1
one += 1;
b[m++] += zero; //先把当前连续0保存起来
}
if(one != 0 && flag == 1 && a[i + 1] == 0) { //连续0连续1再遇到0之前
flag = 0;
if(zero - one > 0)
sum += (zero - one);
if(zero - one < 0) {
b[m++] = sum;
sum = 0; //如果出现类似00111情况,继续连续sum会变小,所以断掉
}
}
if(one >= 1 && a[i + 1] == 0) {
zero = 0;
one = 0;
}
}
for(int i = 0; i < sizeof(b) / sizeof(b[0]); ++i) //这样求整型型数组长度
sum = max(sum, b[i]);
//分类讨论补充个坑点:当全是1,由于字符串非空,此时应该输出-1
if(sum == 0) cout<<-1<<endl;
else cout<<sum<<endl;
return 0;
}
看看大佬们对代码1的评论
改成m也不对,写复杂了导致细节考虑不全
代码2 (√) Accepted 100%
#include<iostream>
using namespace std;
int a[100010];
int main()
{
string s;
cin>>s;
for(int i = 0; i < s.size(); ++i)
a[i] = s[i] - '0';
int sum = -1, zero = 0; //sum初始化-1, 如果出现全是1的情况,输出-1
for(int i = 0; i < s.size(); ++i) {
if(a[i] == 0) zero++; //遇0加1
else zero--; //遇1减1
if(zero < 0) zero = 0; //不满足最大值就重新计算
if(zero > sum) sum = zero; //每一步都进行一次最大值判断
}
if(sum == 0) cout<<-1<<endl;
else cout<<sum<<endl;
//等价于sum == 0 ? cout<<-1 : cout<<sum;
return 0;
}
再给大家看几个截图,大家仔细对比下异同,简洁而高效的同时,适当注释就完美了
输入输出
1110010000010110010100010100101
9
111111111111111111111111111111111111
-1
0111100101
2
1111111000110100100010000000001111100
13
11111111111111111111011111111111111111111111111111111111111111111111
1
题目来源
P5650 基础字符串练习题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
🌼六, 1143: 挤牛奶
题目
三个农民每天清晨5点起床,然后去牛棚给3头牛挤奶
第一个农民在300时刻(从5点开始计时,秒为单位)给他的牛挤奶,一直到1000时刻
第二个农民在700时刻开始,在1200时刻结束
第三个农民在1500时刻开始2100时刻结束
期间最长的至少有一个农民在挤奶的连续时间为900秒(从300时刻到1200时刻),而最长的无人挤奶的连续时间(从挤奶开始一直到挤奶结束)为300秒(从1200时刻到1500时刻)
你的任务是编一个程序,读入一个有N个农民(1 <= N <= 5000)挤N头牛的工作时间列表,计算以下两点(均以秒为单位):
(1) 最长至少有一人在挤奶的时间段
(2) 最长的无人挤奶的时间段
输入格式
第1行:一个整数N
第2..N+1行:每行两个小于1000000的非负整数,表示一个农民的开始时刻与结束时刻
输出格式
一行,两个整数,即题目所要求的两个答案
输入
3
300 1000
700 1200
1500 2100
输出
900 300
分类:贪心,USACO,基础题
解析
本题我采取类似第五题代码2的思路,每遍历一次就判断一次,并执行对应的操作
采用结构体,将每个人开始时间和结束时间联系起来
同时对开始时间排序
yes表示有人喝奶,no表示没人喝奶的时候
Max表示连续状态下当前最晚的时间,Min表示当前最早的时间
好了,想看答案的可以直接跳到√代码
×代码里的思路也是对的,只是实现过程不严谨
代码
敲完后我写了几组数据来测试我的代码,果然发现了一些问题,然后对照问题逐个击破
×代码 Accepted 22%
#include<iostream>
#include<algorithm> //sort()
using namespace std;
struct cow
{
int fir, en;
};
bool cmp(cow x, cow y)
{
return x.en < y.en;
}
int main()
{
struct cow a[5010];
int n;
cin>>n;
for(int i = 0; i < n; ++i)
cin>>a[i].fir>>a[i].en;
sort(a, a + n, cmp); //按结束时间从小到大排序
int yes = 0, no = 0;
//yes当前挤奶最大值,no当前无人最大值
int Max = 0, Min = 1000000; //Min先取最大值,Max先取最小值
for(int i = 0; i < n; ++i) {
Min = min(Min, a[i].fir);
if(a[i].en >= a[i + 1].fir) { //如果能连续
Min = min(Min, a[i + 1].fir); //保留连续段中最早的时间
Max = max(Max, a[i + 1].en); //保留最晚时间
yes = max(yes, Max - Min);
}
else { //如果不连续了
Max = 0;
Min = 1000000; //这样取值便于后续更新数据
no = max(no, a[i + 1].fir - a[i].en);
}
}
cout<<yes<<" "<<no<<endl;
return 0;
}
于是我推倒前面的思路,我知道写成这个样子,再花多一个小时弥补细节,最多也就Accepted 50%,那怎么能行,推倒重干(即使我已通过反例发现错误的部分原因)
✔代码 Accepted 100%
#include<iostream>
#include<algorithm> //sort()
using namespace std;
struct cow
{
int fir, en;
};
bool cmp(cow x, cow y)
{
if(x.fir != y.fir)
return x.fir < y.fir;
}
int main()
{
struct cow a[5010];
int n;
cin>>n;
for(int i = 0; i < n; ++i)
cin>>a[i].fir>>a[i].en;
sort(a, a + n, cmp); //按开始时间从小到大对结构体排序
int Max = 0, Min = 1000000, yes = 0, no = 0;
Max = a[0].en, Min = a[0].fir; //要点4, 开始设个初始,不要在for里再设
for(int i = 1; i < n; ++i) {
if(Max >= a[i].fir) { //要点1,这里是Max >=
Max = max(Max, a[i].en);
Min = min(Min, a[i].fir);
}
else {
yes = max(yes, Max - Min);
no = max(no, a[i].fir - Max); //要点2,这里是 - Max
Max = a[i].en;
Min = a[i].fir; //要点3,重新开始
}
}
yes = max(yes, Max - Min); //要点5, 防止突然杀出个程咬金,最后最长
cout<<yes<<" "<<no<<endl; //yes表示有人,no表示没人
return 0;
}
输入输出
最后一组为输出
3
300 1000
700 1200
1500 2100
900 300
8
200 1200
100 500
400 1000
1700 3100
1400 2800
1500 2000
3900 4200
6000 6100
1700 1800
4
200 800
800 1200
100 1500
1300 2400
2300 0
题目来源
P1143 - 挤牛奶 - New Online Judge (ecustacm.cn)
🌼七, 1148: 混合牛奶
题目
Marry 乳业从一些奶农手中采购牛奶,并且每一位奶农为乳制品加工企业提供的价格是不同的
此外,就像每头奶牛每天只能挤出固定数量的奶,每位奶农每天能提供的牛奶数量是一定的
每天 Marry 乳业可以从奶农手中采购到小于或者等于奶农最大产量的整数数量的牛奶
给出 Marry 乳业每天对牛奶的需求量,还有每位奶农提供的牛奶单价和产量
计算采购足够数量的牛奶所需的最小花费
注:每天所有奶农的总产量大于 Marry 乳业的需求量
输入格式
第一行两个整数n和m,分别表示牛奶总量,提供奶农数量。(0<=n<=2000000, 0<=M<=5000)
接下来m行,每行两个整数pi和ai,分别表示第i个奶农牛奶单价和产奶上限(0<=pi<=1000,0<=ai<=2000000)
输出格式
输入
100 5
5 20
9 40
3 10
8 80
6 30
输出
630
解析
和上面那题比起来,简单很多,不需要考虑什么条件,只需要借助结构体,按价格从小到大排序,再对数组遍历就好
代码
#include<iostream>
#include<algorithm> //sort()
using namespace std;
struct man
{
int price, num;
};
bool cmp(man x, man y)
{
return x.price < y.price;
}
int main()
{
struct man a[5010];
int n, m;
cin>>n>>m; //m为奶农数量,n为需要的牛奶数量
for(int i = 0; i < m; ++i)
cin>>a[i].price>>a[i].num;
sort(a, a + m, cmp);
int sum = 0, ans = 0; //sum已有的牛奶数量,ans为最小花费
for(int i = 0; i < m; ++i) {
if(sum + a[i].num <= n) {
sum += a[i].num;
ans += a[i].num * a[i].price;
}
else {
ans += (n - sum) * a[i].price;
break; //不要漏
}
}
cout<<ans<<endl;
return 0;
}
输入输出
100 5
5 20
9 40
3 10
8 80
6 30
630
题目来源
P1148 - 混合牛奶 - New Online Judge (ecustacm.cn)
🌼八,2035: [蓝桥杯2022初赛] X进制减法
关于这道题不想说什么了,代码果然要从模仿开始,在浪费了2小时,在各种细节中挣扎依旧失败后,终于下定决心模仿别人代码
题目
进制规定了数字在数位上逢几进一
X 进制是一种很神奇的进制,因为其每一数位的进制并不固定!
例如说某种X 进制数,最低数位为二进制,第二数位为十进制,第三数位为八进制:
则 X 进制数321 转换为十进制数为65。65=3*(2*10)+2*(2)+1*(1)
现在有两个 X 进制表示的整数 A 和 B,但是其具体每一数位的进制还不确定
只知道 A 和 B 是同一进制规则,且每一数位最高为 N 进制,最低为二进制
请你算出 A − B 的结果最小可能是多少
请注意,你需要保证 A 和 B 在 X 进制下都是合法的,即每一数位上的数字要小于其进制
输入格式
第一行一个正整数 N,含义如题面所述
第二行一个正整数 Ma,表示 X 进制数 A 的位数
第三行 Ma 个用空格分开的整数,表示 X 进制数 A 按从高位到低位顺序各个数位上的数字在十进制下的表示
第四行一个正整数 Mb,表示 X 进制数 B 的位数
第五行 Mb 个用空格分开的整数,表示 X 进制数 B 按从高位到低位顺序各个数位上的数字在十进制下的表示
请注意,输入中的所有数字都是十进制的
30%的测试数据:2≤N≤10,1≤Ma,Mb≤8
100%的测试数据:2≤N≤1000,1≤Ma,Mb≤100000,B≤A
输出格式
输出一行一个整数,表示X 进制数A − B 的结果的最小可能值转换为十进制后再模 1000000007 的结果
输入
11
3
10 4 0
3
1 2 0
输出
94
数据范围与提示
当进制为:最低位 2 进制,第二数位 5 进制,第三数位 11 进制时,减法得到的差最小
此时A 在十进制下是108,B 在十进制下是 14,差值是 94
类型:模拟,贪心,基础题
审题很重要,1,B <= A 2,输入数字为十进制 3,所有数字小于输入的N进制
4,结尾的数据范围与提示,已经告诉你本题贪心的思路了
代码1 Accepted 50%
观察完大佬代码后,我凭着自己的感觉敲完,代码结尾注释有错误解析
#include<iostream>
using namespace std;
const int mod = 1000000007;
int a[100010], b[100010];
int main()
{
int N, ma, mb;
cin>>N; //感觉N没什么卵用
cin>>ma; for(int i = ma - 1; i >= 0; --i) cin>>a[i];//反着存,便于后续遍历
cin>>mb; for(int i = mb - 1; i >= 0; --i) cin>>b[i];
long long ans = 0, pro = 1, aaa = 0, bbb = 0; //pro(duct)所有前面的乘积
for(int i = 0; i < ma; ++i) { //从最小位数开始
aaa = (aaa + a[i] * pro) % mod;
bbb = (bbb + b[i] * pro) % mod;
pro = (pro * max(max(a[i], b[i]) + 1, 2)) % mod;
ans = (aaa - bbb) % mod;
}
cout<<ans<<endl;
return 0;
}
//先取余再加与加完再取余结果一样,但先取余再减与先减再取余结果不一定一样
//比如32 - 13 == 19, 19 % 7 == 5,但是32 % 7 - 13 % 7 == -2,所以代码1只对了50%
//针对这个情况,我们需要先减再取余,这时就要在计算过程中对a[i] - b[i]取余
代码2 Accepted 30%
改进后通过率更少了???
#include<iostream>
using namespace std;
const int mod = 1000000007;
int a[100010], b[100010];
int main()
{
int N, ma, mb;
cin>>N; //感觉N没什么卵用
cin>>ma; for(int i = ma - 1; i >= 0; --i) cin>>a[i];//反着存,便于后续遍历
cin>>mb; for(int i = mb - 1; i >= 0; --i) cin>>b[i];
long long ans = 0, pro = 1; //pro(duct)所有前面的乘积
for(int i = 0; i < ma; ++i) { //从最小位数开始
ans += ((a[i] - b[i]) * pro) % mod;
pro = (pro * max(max(a[i], b[i]) + 1, 2)) % mod;
}
cout<<ans<<endl;
return 0;
}
//可是代码2又犯了一个错误,你需要把1行拆开成2行
//为什么呢,举个例子,A + B % 100 与 (A + B) % 100显然不一样
//当A + B 小于 mod(1000000007)时,输出正确,反之错误
//这就是为什么代码2只对了30%
代码3 Accepted 100%
#include<iostream>
using namespace std;
const int mod = 1000000007;
int a[100010], b[100010];
int main()
{
int N, ma, mb;
cin>>N; //感觉N没什么卵用
cin>>ma; for(int i = ma - 1; i >= 0; --i) cin>>a[i];//反着存,便于后续遍历
cin>>mb; for(int i = mb - 1; i >= 0; --i) cin>>b[i];
long long ans = 0, pro = 1; //pro(duct)所有前面的乘积
for(int i = 0; i < ma; ++i) { //从最小位数开始
ans += ((a[i] - b[i]) * pro);
ans %= mod;
pro = (pro * max(max(a[i], b[i]) + 1, 2));
pro %= mod;
}
cout<<ans<<endl;
return 0;
}
解析
1,
第9,10行将X进制数A和B,反着存到数组a,b里,便于后续从位数最小的开始遍历(以便实现前面乘积的累乘)
2,
14~17行为核心代码,
第14行令ans(answer) = A,B相同位数相减,再*乘积pro
第15行ans 对 mod取余
第16行实现乘积累乘,其中max(a[i], b[i])表示A,B中数字较大的,+1可令进制最小,保证相减得到最小值,同时与2比较,是为了保证a[i], b[i]都为0时,进制为最小的二进制
第17行再取余 mod
题目来源
P2035 - [蓝桥杯2022初赛] X进制减法 - New Online Judge (ecustacm.cn)
🌼九, 1125: 打地鼠 未完成
题目
最近淘淘沉迷上了打地鼠游戏,游戏开始后,会在地板上冒出来一些地鼠,你可以用榔头去敲击这些地鼠
每个地鼠被敲击后,将会增加相应的游戏分值。可是,所有地鼠只会在地上出现一段时间(而且消失后再也不会出现)
每个地鼠都在0时刻冒出,但停留的时间可能是不同的,而且每个地鼠被敲击后增加的游戏分值也可能是不同
由于淘淘经常玩这个游戏,以致于敲击每个地鼠的时间均为1s,他想知道自己最多能得到多少分
输入格式
输入包含三行,第一行包含一个整数n(1<=n<=100000)表示有nn个地鼠从地上冒出来
第二行n个用空格分隔的整数表示每个地鼠冒出后停留的时间t(1<=t<=50000)
第三行n个用空格分隔的整数表示每个地鼠被敲击后会增加的分值v(0<=v<=1000)
每行中第i个数都表示第i个地鼠的信息
输出格式
输出一个整数表示淘淘最多得到的分数
输入
5
5 3 6 1 4
7 9 2 1 5
输出
24
分类:贪心,进阶题
解析
看到题目第一时间想到结构体
从1开始遍历,时间相同的情况下,先敲分数最高的地鼠(贪心),如果下一秒没有地鼠消失,就敲分数第二高的,这里需要排序
每敲完一个,分数加上,所有成员的剩余时间 - 1,所有成员时间 - 1不怎么好实现,所以我们换种思路达到相同效果:借助变量t,时间 >= t 才考虑(t初始为1),每加入一个分数,t++
本题单独对时间或者分数排序都不好,所以我们同时对两个下手
如果时间相同,返回分数大的,否则返回时间小的(cmp)(compare)
×代码1 Accepted30%
#include<iostream>
#include<algorithm> //sort()
using namespace std;
struct mice
{
int time, grade;
};
bool cmp(mice x, mice y)
{
if(x.time == y.time)
return x.grade > y.grade;
else
return x.time < y.time; //同时对时间和分数排序
}
int main()
{
struct mice a[100010];
int n, sum = 0, t = 1;
cin>>n;
for(int i = 1; i <= n; ++i) cin>>a[i].time;
for(int i = 1; i <= n; ++i) cin>>a[i].grade;
sort(a + 1, a + n + 1, cmp);
for(int i = 1; i <= n; ++i)
if(a[i].time >= t) { //如果这个地鼠时间没结束
sum += a[i].grade;
t++;
}
cout<<sum<<endl;
return 0;
}
核心思路就排序 + 最后三行代码,可惜只过了30%
这时我们需要着重对比,开头和结尾是否✔,或者想想反例,或者是否超限,先在草稿纸举例模拟一遍
看了眼大佬们的代码,全是什么并查集,堆栈.....无语了,还没学,不能整点纯贪心的吗
√代码2 Accepted 100%
不浪费时间了,等以后学了堆栈什么的再回来,pop(), push()我也懂,可是还没系统学习
输入输出
题目来源
P1125 - 打地鼠 - New Online Judge (ecustacm.cn)
🌼十, 1161: 三值排序
题目
给一个长度为n的数组,其中数组各元素的值仅为1、2、3
求排成升序的最少交换次数
输入格式
第一行为正整数n,不超过1000
接下来n行,每行一个整数表示数组元素
输出格式
输出一个数字表示答案
输入
9
2
2
1
3
3
3
2
3
1
输出
4
类型:贪心,USACO,基础题
解析
首先明确,最少交换次数(两数位置的交换),不是最少移动次数
开始我想找普适意义的规律,就是直接通过1,2,3的个数和相对位置,得出答案,发现不行
思路2:
先对数组按从小到大排序
通过原排列与现排列的比较
排序后为1原来不为1的数量 + 排序后为2原来不为2的数量 - 排序后为2原来为1的数量(即减去重复部分),比如
2 2 1 3 3 3 2 3 1
1 1 2 2 2 3 3 3 3
则最少交换次数 = 2 + 3 - 1 = 4
这里面就不用对3考虑了,因为交换1,3或者2,3的次数包含在里面
按照这个思路,得到了Accepted 88%的代码
代码1 Accepted 88%
#include<iostream>
#include<algorithm> //sort()
using namespace std;
int a[1010];
int b[1010];
int main()
{
int n;
cin>>n;
for(int i = 0; i < n; ++i) {
cin>>a[i];
b[i] = a[i];
}
sort(a, a + n); //a[i]是排序后的,b[i]原排列
int one = 0, two = 0, repeat = 0;
for(int i = 0; i < n; ++i) {
if(a[i] == 1 && b[i] != 1)
one++;
if(a[i] == 2 && b[i] != 2)
two++;
if(a[i] == 2 && b[i] == 1)
repeat++;
}
cout<<one + two - repeat<<endl;
return 0;
}
代码2 Accepted 100%
提供网上的一种思路,要我证明也不会
#include<iostream>
#include<algorithm> //sort()
using namespace std;
int a[1010];
int b[1010];
int main()
{
int n;
cin>>n;
for(int i = 0; i < n; ++i) {
cin>>a[i];
b[i] = a[i];
}
sort(a, a + n); //a[i]是排序后的,b[i]原排列
int one = 0, two = 0, three = 0;
for(int i = 0; i < n; ++i) {
if(a[i] == 1 && b[i] != 1)
one++;
if(a[i] == 2 && b[i] == 3)
two++;
if(a[i] == 3 && b[i] == 2)
three++;
}
cout<<one + max(two, three)<<endl;
return 0;
}
输入输出
9
2 2 1 3 3 3 2 3 1
4
10
2 2 2 3 1 1 1 1 3 2
5
题目来源
P1161 - 三值排序 - New Online Judge (ecustacm.cn)
🌼十一,P1614 爱与愁的心痛
题目
最近有 n 个不爽的事,每句话都有一个正整数刺痛值(心理承受力极差)。爱与愁大神想知道连续 m 个刺痛值的和的最小值是多少,但是由于业务繁忙,爱与愁大神只好请你编个程序告诉他
输入格式
第一行有两个用空格隔开的整数,分别代表 n 和 m
第 2 到第 (n + 1) 行,每行一个整数,第 (i + 1) 行的整数 ai 代表第 i 件事的刺痛值 ai
输出格式
输出一行一个整数,表示连续 m 个刺痛值的和的最小值是多少
输入
8 3 1 4 7 3 1 2 4 3
输出
6
数据范围
对于30%的数据,n <= 20;对于60%的数据,n <= 100;对于90%的数据,n <= 10^3;对于100%的数据,n <= 3 * 10^3,ai >=1 && ai <= 100
类型:模拟,贪心,枚举,入门题
解析
两个for循环遍历就行了,需要注意的是,遍历范围不一样,受输入的m和n制约
我一开始令外层for中i < n - 2,内层for中j < i + 3,是误把示例当结果了,忘记了还有m
后来才改成外层i < n - m + i,内层j < i + m
代码
#include<iostream>
using namespace std;
int a[3010];
int main()
{
int n, m, sum, Min = 300000; //300000 = 3000 * 100
cin>>n>>m;
for(int i = 0; i < n; ++i) cin>>a[i];
for(int i = 0; i < n - m + 1; ++i) { //这里是i < n - m + 1
sum = 0;
for(int j = i; j < i + m; ++j) { //这里是j < i + m
sum += a[j];
}
Min = min(Min, sum);
}
cout<<Min<<endl;
return 0;
}
输入输出
15 4
13 11 21 8 47 21 4 22 52 49 96 1 1 98 49
53
题目来源
P1614 爱与愁的心痛 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
🌼十二,P2240 【深基12.例1】部分背包问题
题目(五要素)
阿里巴巴走进了装满宝藏的藏宝洞。藏宝洞里面有 N(N≤100) 堆金币,第 i 堆金币的总重量和总价值分别是 mi,vi(1≤mi,vi≤100)。阿里巴巴有一个承重量为 T(T≤1000) 的背包,但并不一定有办法将全部的金币都装进去。他想装走尽可能多价值的金币。所有金币都可以随意分割,分割完的金币重量价值比(也就是单位价格)不变。请问阿里巴巴最多可以拿走多少价值的金币?
输入格式
第一行两个整数 N,T
接下来 NN 行,每行两个整数mi,vi
输出格式
一个实数表示答案,输出两位小数
输入
4 50 10 60 20 100 30 120 15 45
输出
240.00
类型:贪心,普及/提高-
分析
采用结构体和bool函数cmp结合sort(),然后if, else对单位价值讨论就好
bug1:
一开始int ans = 0,导致了样例总是输出160.00,因为整数相除会向下取整,比如 20 / 30 = 0, 2 / 5 = 2, -2 / 5 = -2,然后把int ans = 0改成double ans = 0
(过了20%)
bug2:
第29行代码 ans += (T / a[i].w) * a[i].p 的 T 改成 double(T) 进一步提高了精度,乘除时最好转化为double,或者干脆在结构体成员那 double w, p;
(过了60%)
bug3:
第24行用了 if(T - a[i].w >= 0),第28行又用了 if(T - a[i].w < 0),所以当24行第一个if满足完,本来第二个if是不满足的,但由于第一个里 T -= a[i].w,导致第二个也满足了,会多执行一次第二个if的内容.........第二个if要改else崩溃了,bug3找了3小时才发现俩if有问题😰😰😰
(过了100%)
代码
这题很简单,但是坑很多,还得加强基础,外加细心
#include<iostream>
#include<stdio.h> //printf()
#include<algorithm> //sort()
using namespace std;
struct gold
{
double w, p; //weight, price //要点1
};
bool cmp(gold x, gold y)
{
return x.p * y.w > y.p * x.w; //要点2
}//防止精度缺失采取交叉相乘,虽然本题没什么用
int main()
{
struct gold a[110];
int N, T;
double ans = 0.00; //要点3
cin>>N>>T;
for(int i = 0; i < N; ++i)
cin>>a[i].w>>a[i].p;
sort(a, a + N, cmp);
for(int i = 0; i < N; ++i) {
if(T - a[i].w >= 0) {
T -= a[i].w;
ans += a[i].p;
}
else { //要点4
ans += (T / a[i].w) * a[i].p;
break;
}
}
printf("%.2lf", ans);
return 0;
}
题目来源
P2240 【深基12.例1】部分背包问题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
总结
贪心暂时告一段落,在我写完第12题,打算到力扣做一些中等难度的题时,遇到了极大阻力,遇到了各种结合dp,二叉树,二分,高精度,堆栈,优先队列...的题,可我目前只会一点贪心,高精度加法和排序
由此,刚下单《啊哈算法》,跟着书本敲,再结合一百小时的blibli蓝桥杯算法视频,补补基础