日常刷题记录
合并果子
题目解析
有一堆果子,每次可以将两小堆合并,耗费的体力为合并得到的这一堆的重量,给你每个小堆果子的重量,把这些果子合并成一堆后消耗的最小体力是多少
算法思路
由题意得知只要每次将重量最小的两堆果子合并就行了,你可以每合并一次就调用一次排序,但是STL提供了优先队列的数据结构,可以很好的完成这题,每次把前两个出队然后合并成一个再入队,直到队列里就剩一个元素。优先队列默认是降序的,所以定义的时候要注意加上greater<int>
代码实现
#include <bits/stdc++.h>
using namespace std;
int main() {
priority_queue<int, vector<int>, greater<int> > q;
int n;cin >> n;
for(int i=1;i<=n;i++){
int x;cin >> x;
q.push(x);
}
int ans=0;
while(q.size()>1){
int tem=q.top();
q.pop();
tem+=q.top();
q.pop();
q.push(tem);
ans+=tem;
}
cout << ans << endl;
return 0;
}
中位数
题目解析
要求把序列里前奇数项的中位数全都输出,例如前1项、前3项、前5项…前 N + 1 2 \frac{N+1}{2} 2N+1项
算法思路
这题用vector动态数组就可以实现了,一开始想用优先队列,但是由于优先队列只能访问首元素,所以不合适。向数组中依次插入元素,当元素的个数为奇数个的时候,取中间的元素(第(i-1)/2
个元素)就可以了。重点在于怎么样插入,想象在打扑克牌时摸牌后插入的操作,只需要找到序列里第一个比当前数大的数,然后在其前面插入。STL正好提供了这样一个函数:upper_bound(序列首,序列尾,查找的数)
具体看以下代码。upper_bound
代码实现
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n;cin >> n;
vector<int>a;
for(int i = 1; i <= n ; i++)
{
int x;cin >> x;
a.insert(upper_bound(a.begin(),a.end(),x),x);
if(i%2 == 1)
{
cout<<a[(i-1)/2]<<endl;
}
}
return 0;
}
python也有类似的办法但是最后一个测试点超时了,仅作为学习了解
import bisect
n=int(input())
nums=list(map(int,input().split()))
vec=[]
for i in range(1,n+1):
bisect.insort(vec,nums[i-1])#insort()函数将元素插入到vec中,并保持vec的有序性
#也可以这么写
# x=nums[i-1]
# pos = bisect.bisect_right(vec, x)
# vec.insert(pos, x)
if(i%2==1):
print(vec[(i-1)//2])
Maximum Rounding
题目解析
输入一个很长的数,从最后一位开始四舍五入,最后可以得到多大的数
算法思路
因为数字很大所以肯定是要采用字符串,观察发现当我们从最后一位开始向前遍历,如果对当前这一位“五入”,其后面的数将全变成0,如果从尾到头一次“五入”都没有发生,那么就直接输出这个数。于是我们可以用一个变量p来存放最近一次进行“五入”的位置,在遍历结束后把p这一位以后的数字全变成0即可。对于每一次“五入”操作,我们需要把他的前一位进位,因为每次之和“5”来比较从而判断是否进位,因为是字符比较,所以你只要把他的ascii码往后移动一位就行了,不用担心9往后移动后出现的’ : ',需要注意的是,这样一来遍历范围得为从后遍历到第1个元素而不是第0个元素,以免越界。
代码实现
python
T = int(input())
for _ in range(T):
s=list(input())
p=-1
for i in range(len(s)-1,0,-1):
if s[i]>='5':
s[i-1]=chr(ord(s[i-1])+1)
p=i
if s[0]>='5':
print('1',end="")
p=0
if p!=-1:
for i in range(p,len(s)):
s[i]='0'
print(''.join(s))
C++
#include <bits/stdc++.h>
using namespace std;
int main(){
int t;cin>>t;
while(t--){
string s;cin>>s;
int p=-1;
for(int i = s.size()-1; i; i--){
if(s[i]>='5'){
s[i-1]++;
cout<<s<<endl;
p=i;
}
}
if(s[0]>='5')cout<<'1',p=0;
for(int i=p;i<s.size();i++)
s[i]='0';
cout<<s<<endl;
}
}
个人练习赛记录
E - A - B数对
总结
这题我是通过了,用的方法还是比较巧妙的,所以记录一下。通过一个字典来记录每一个数字出现的次数,然后遍历字典,先把当前的数作为A然后到剩下的里找B,B出现的次数也就是数对的个数,再把当前的数作为B然后到剩下的里找A,A出现的次数也就是数对的个数,但是要注意最后的结果要除2,因为有重复的计算
n, c = map(int, input().split())
nums = list(map(int, input().split()))
count = {}
for num in nums:
if num in count:
count[num] += 1
else:
count[num] = 1
result = 0
for num in nums:
result += count.get(num - c, 0)
result += count.get(num + c, 0)
print(result // 2)
F - Sierpinski carpet
总结
这题样例通过但是RuntimeError,原因是因为输入范围包含了0,但我并没有考虑到。
这题和之前做过的一个题目很像,我马上想到了递归,代码如下。
def fun(data, x, i, j):
if x == 3:
data[i+1][j+1] = "."
else:
for ii in range(i+x//3, i+2*x//3):
for jj in range(j+x//3, j+2*x//3):
data[ii][jj] = "."
fun(data, x//3, i, j)
fun(data, x//3, i, j+x//3)
fun(data, x//3, i, j+2*x//3)
fun(data, x//3, i+x//3, j)
fun(data, x//3, i+x//3, j+2*x//3)
fun(data, x//3, i+2*x//3, j)
fun(data, x//3, i+2*x//3, j+x//3)
fun(data, x//3, i+2*x//3, j+2*x//3)
n = int(input())
size = 3 ** n
data = [["#" for i in range(size)] for j in range(size)]
if n==0:
print("#")
else:
fun(data, size, 0, 0)
for k in data:
print("".join(k))
当然还有一种更巧妙的方法,是看同学的代码学的。
n=int(input())
a="#"
c="."
a=a*3+'\n'+a+c+a+'\n'+a*3
if n==0:
print("#")
elif n==1:
print(a)
else:
t=1
while n>1:
d=a.split('\n')
if t!=1:
d.pop()
a=""
for i in d:
a+=i*3
a+="\n"
for i in d:
a=a+i+"."*3**t+i+"\n"
for i in d:
a+=i*3+'\n'
n-=1
t+=1
print(a)
Atcoder Beginner Contest 364记录
这次比赛做出了前三题。
D - K-th Nearest
总结
题目的意思是,先给一个数组a,然后找出a中所有元素与b的差距之中第k大的元素。这道题我拿正常方法做超时,结束之后看了一下题解,简单讲讲思路。
首先把a,b,k都存入到各自的数组中,然后把a从小到大排序。同时遍历b和k,b在一个数轴上,以b点为中心,a中的元素是分布在b的左右两侧的点,那么这些点到b点的距离就是他们之间的差值。我们要找到差距第k大的元素,也就是找到以b为圆心,某一长度为半径,圆内刚好包含了k个元素时,圆的最小半径。用两个指针lo和hi,从0到一个很大的数(2E8)开始二分查找圆的半径,一部分不含解,一部分含有可能的解,所以每一次查找就可以对折一次直到找出解。x为lo和hi的平均值,计算以b为中心,半径为x的范围内中包含的元素数量。如果数量大于当前的k值hi指针减小,否则lo指针增大。
下面我画了示意图,是样例里的第一个情况:a=[-3,-1,5,6],b=-2,k=3
import bisect
N, Q = map(int, input().split())
a = list(map(int, input().split()))
a.sort()
b = []
k = []
for _ in range(Q):
line = input().split()
b.append(int(line[0]))
k.append(int(line[1]))
for i in range(Q):
lo = 0
hi = 10**9+1
while lo < hi:
x = (lo + hi) // 2
l = b[i] - x
r = b[i] + x
cnt = bisect.bisect_right(a, r) - bisect.bisect_left(a, l)
if cnt >= k[i]:
hi = x
else:
lo = x + 1
print(lo)
E - Maximum Glutton
总结
题目意思是你有 N 道菜,每道菜的甜度和咸度分别是 A 和 B。选择最多数量的菜品,使得所有选择的菜品的总甜度不超过 X,总咸度不超过 Y。
这题需要用到动态规划
要保证甜度不超标和咸度不超标,两者都是一个动态的值,我们可以直接把其中一者的范围给定死,只考虑甜度范围内的情况,那么只要再去判断咸度有没有超标就行了。而我们每次添加一道菜,咸度都会增多,为了能吃更多的菜,得尽量降低咸度。最后我们找到既能满足不超出咸度又能保证甜度最大的那个位置是多少道菜。
INF
:用来初始化dp
表,表示“无穷大”。dp[i][j][k]
:前i
道菜中,选择j
道菜时,甜度总和为k
的情况下的最小咸度总和。- 初始化 dp 表,大小为
(N+1) x (N+1) x (X+1)
,其中所有值都为INF
。 d[0][0][0]
设置为0
,表示没有选择任何菜品时,甜度和咸度总和都为0
。
动态规划填表
- 对于每道菜,读取它的甜度
A
和咸度B
。 - 遍历前
i
道菜,选择j
道菜时,检查甜度总和k
:- 如果不选择当前菜,
dp[i + 1][j][k]
等于dp[i][j][k]
。 - 如果选择当前菜,检查是否可以添加该菜品(即总甜度不超过
X
),如果可以添加菜品,那么我们找到添加这道菜后的甜度能匹配到的最小咸度,如果加了这道菜后咸度可以更小那就添加,否则仍然不添加。即更新dp[i + 1][j + 1][k + A]
为自身与当前选择的咸度总和dp[i][j][k] + B
的最小值。
- 如果不选择当前菜,
输出
- 从
dp[N][j][k]
中查找满足条件的j
,即使得背包容量为k
时,最小的咸度总和dp[N][j][k]
小于等于Y
。 - 输出满足条件的最大菜品数量
j + 1
,并结束程序。
N, X, Y = map(int, input().split())
INF = 10**9
dp=[[[INF]*(X+1)for j in range(i+1)]for i in range(N+1)]
dp[0][0][0]=0
for i in range(N):
A, B=map(int, input().split())
for j in range(i+1):
for k in range(X+1):
dp[i+1][j][k]=min(dp[i+1][j][k], dp[i][j][k])
if k + A <= X:
dp[i+1][j+1][k+A]=min(dp[i+1][j+1][k+A], dp[i][j][k]+B)
for j in range(N - 1,-1,-1):
for k in range(X + 1):
if dp[N][j][k] <= Y:
print(j+1)
exit()
C++学习笔记
队列queue
-
创建队列:
queue<int> q1; // 使用默认构造函数创建一个空队列 queue<int> q2({1, 2, 3}); // 使用初始化列表创建队列(需要自定义构造)
-
添加元素:
push()
:将一个元素添加到队列的末尾。q.push(10); // 将10添加到队列末尾
-
移除元素:
pop()
:从队列的前端移除一个元素。q.pop(); // 移除队列前端的元素
-
访问元素:
front()
:获取队列前端的元素,但不移除它。int frontElement = q.front(); // 获取队列前端的元素
back()
:获取队列末尾的元素,但不移除它。int backElement = q.back(); // 获取队列末尾的元素
-
检查队列状态:
empty()
:检查队列是否为空。if (q.empty()) { std::cout << "队列为空" << std::endl; }
size()
:获取队列中元素的数量。size_t numElements = q.size(); // 获取队列中的元素数量
-
清空队列:
clear()
:虽然std::queue
没有直接提供clear()
函数,但可以通过循环pop()
所有元素来实现清空操作。
双端队列 deque
Q.push_back()
在队尾插入一个元素
Q.push_front()
在队首插入一个元素
Q.push_back()
在队尾移除一个元素
Q.push_front()
在队首移除一个元素
优先队列 priority_queue
为什么要用优先队列?
- 给你n个数,对于所有元素来说,找到他前面(包括他)最大的元素
定义
priority_queue
是一个模板类,基本定义如下:
#include <queue>
#include <vector>
std::priority_queue<T, Container, Compare>
- T:队列中存储的元素类型。
- Container:底层容器,默认是
std::vector<T>
。 - Compare:比较器,用于决定元素的优先级,默认是
std::less<T>
(即最大堆)。
常见操作
-
创建优先队列:
priority_queue<int> pq1; // 默认最大堆,降序 priority_queue<int, vector<int>, greater<int>> pq2; // 最小堆,升序
-
添加元素:
- 使用
push()
方法将元素添加到优先队列中。pq.push(10); // 添加元素 10
- 使用
-
移除元素:
- 使用
pop()
会移除堆顶元素,但不会返回它。pq.pop(); // 移除堆顶元素
- 使用
-
访问元素:
使用top()
访问堆顶元素(即优先级最高的元素),但不移除它。int topElement = pq.top(); // 获取堆顶元素
-
检查队列状态:
- 使用
empty()
方法检查优先队列是否为空。if (pq.empty()) { std::cout << "优先队列为空" << std::endl; }
- 使用
size()
方法获取优先队列中的元素数量。size_t numElements = pq.size(); // 获取元素数量
- 使用
Set集合
set
的定义
在 C++ 中,set
的定义如下:
#include <set>
set<T> mySet;
其中 T
是存储在 set
中元素的类型。你还可以指定比较函数,默认是 std::less<T>
,用于决定元素的排序方式:
set<T, Compare> mySet;
常见操作
-
插入元素
mySet.insert(value); // 插入一个元素
如果插入的元素已经存在,
set
不会插入重复的元素。insert
返回一个pair
,其中第一个元素是迭代器,指向插入的元素(或者指向已经存在的元素),第二个元素是一个布尔值,表示插入是否成功。 -
删除元素
mySet.erase(value); // 删除一个元素
mySet.erase(iterator); // 通过迭代器删除元素
mySet.erase(range_begin, range_end); // 删除一个范围内的所有元素
-
查找元素
auto it = mySet.find(value); // 查找一个元素,返回指向该元素的迭代器,如果未找到则返回 `end()`
-
访问元素
set
中的元素不能通过下标访问,但可以通过迭代器访问:for (auto it = mySet.begin(); it != mySet.end(); ++it) { // 访问 *it 获取元素 }
-
检查元素是否存在
if (mySet.count(value) > 0) { // 元素存在 }
count
方法返回指定值的出现次数,在set
中它要么返回0
(元素不存在),要么返回1
(元素存在)。 -
获取元素个数和清空
size_t size = mySet.size(); // 获取元素个数
mySet.clear(); // 清空所有元素
-
其他操作
-
检查是否为空:
bool isEmpty = mySet.empty();
-
获取第一个和最后一个元素:
auto first = mySet.begin(); // 指向第一个元素 auto last = mySet.rbegin(); // 指向最后一个元素
-
范围操作:
auto range = mySet.equal_range(value); // 获取一个范围,包含所有等于指定值的元素
-
自定义排序:
set<int, greater<int>> mySet; // 使用大于号自定义排序
-
Multiset 可重集合
multiset是一个常用的容器,它可以看成一个序列,插入一个数,删除一个数都能够在O(logn)的时间内完成,而且他能时刻保证序列中的数是有序的,而且序列中可以存在重复的数
Map
Map是一种基于红黑树的容器 支持快速的插入查找和删除操作,并且保证了内部元素的有序性 其中每一个元素都有一个key和一个相关联的val组成
时间复杂度Olog(n)
Map<T, T> mp; int , double,
Mp.find();
Mp.insert({key, val});
Mp.erase(key)
删除一个key //释放空间
Mp.count(key)
统计这个下标出现了多少次
Mp.size();
Mp.clear();
MP.empty();
map里的元素默认是从小到大排序的 怎么样设置从大到小排序?
map<int, int, greater<int>>mp;
for(auto [x = key, c = val] : mp)
遍历map
Unordered_Map
- unordered_map基于哈希表的map
- 在使用方面 和map没有任何区别
- map:优点:有序性, 效率十分稳定 Olog(n)
- unordered_map:优点:查找速度非常的快 近似O(1)
- 缺点:无序, 插入操作的效率不如map
- 如果说你需要遍历整个map 且 想要的是有序的(for(auto[x, c]: mp) 那么就用map
- 其他就用unordered_map 在codeforces一定会TLE
- 能让你用map那么大概率能用unordered_map 有概率会卡unordered_map
- 相反亦然 有的题你用unordered_map能过 有可能map会被卡
upper_bound
upper_bound
函数返回一个迭代器,指向一个在有序范围内第一个大于给定值 value
的元素。简单来说,它找的是第一个比 value
大的元素的位置。如果所有元素都小于或等于 value
,则返回 last
。upper_bound
通常用于在排序好的容器中查找某个值的插入位置,以保持容器的有序性。lower_bound
同理
#include <bits/stdc++.h>
using namespace std;
int main(){
vector<int> vec{1,4,7,8,12};
int x=11;
vec.insert(upper_bound(vec.begin(),vec.end(),x),x);//在第一个比x大的位置插入x
for(auto i:vec) cout<<i<<" ";
}
输出多行用空格分隔的元素
#include<bits/stdc++.h>
using namespace std;
int main()
{
for(int i=0;i<2;i++){
for(int i=0;i<=9;i++){
cout<<i<<" \n"[i==9];//如果i==9,则在i后跟一个换行,否则跟一个空格
}
}
}