文章目录
The Second Week
一个人只要知道自己为什么而活,就可以忍受任何一种生活。 ————尼采
一、前言
基本习惯用clion啦,目前在适应ClangFormat,据说可以规范代码格式。好像这个软件确实是比devC++高级一点点,主要是多熟悉几个编程软件吧,而且目前用的是纯英文的。
周一又打了蓝桥杯模拟赛,这次div1和div2是一起的,拿了四十名,422分13.01h,很多在gh的题原来都是前几道了啊,可是我还是不会做,这周给这类型常出题开个板块,大家都好厉害啊。我好像只能做签到题的水平唉。连通域的概念。
周三打了排位赛,又是二十…还有一小时的时候电脑死机了…当时气的要死直接就走了,有一题交了八次…后来学姐告诉我是数组越界runtime error…加油吧反正
周五的排位赛很多打得好的都去打别的比赛了,记名次也没有意义,反正当时状态还不错感觉,可惜有一题挺简单的没有做出来。
二、算法
ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
就是这一行代码!让我一直TLE!试了好多次才知道是因为这串代码,它的作用是关闭C和C++标准流之间的同步,并将cin和cout流与标准输入和输出分别解绑。这可以提高C++程序中输入/输出操作的性能。(听不懂反正可以减少时间,以后有意识可以写一下)
1.图/树
<1>(洛谷P8605)
模拟赛的时候怎么都没办法ac,因为是ioi赛制拿了八十分,用dfs做的,一直MLE,但也不知道该怎么修改了,第一段代码是赛场上代码,既然没有ac就不写题解了,后面一段代码是后来补题的时候,看了几个人的题解,确实用树的思想可能会更容易一点。
代码:
#include<iostream>
using namespace std;
int n, m;
int a[10005][10005];
int dfs(int t,int q){
if (q == 4) {
return 1;
}
int total = 0;
for (int j = 1; j <= n; j++) {
if (a[t][j] == 1) {
a[t][j] = 0;
a[j][t] = 0;
total += dfs(j, q + 1);
a[t][j] = 1;
a[j][t] = 1;
}
}
return total;
}
int main() {
cin >> n >> m;
int ans = 0;
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
a[u][v] = 1;
a[v][u] = 1;
}
for (int i = 1; i <= n; i++) {
int total = dfs(i, 1);
ans += total;
}
cout << ans;
}
题解:
题意要求给出若干个节点和双向连接线路,经过俩个与原点不同的不重复点后到达目的地,目的地可以是原点,即路线总长度为4的情况有几种。
数组du储存了每个点的度,(度指每个点连了几条线),固定一边俩点为中间俩点,计算它的首尾有几种情况,只需(前者的度-1)(后者的度-1),就是首尾的可能情况,因为是双向,所以再2,其中u和v数组分别储存的是线路的俩点,每条线路不重复,所以逐一作为中间俩点进行遍历,不会有重复。另外注意n和m的数据范围不一样,分别影响du和u,v的范围,以及开long long。
代码:
#include<iostream>
using namespace std;
int du[10005]={0},u[100005],v[100005];
int main() {
int n,m;
long long int sum=0;
std::cin>>n>>m;
for(int i=0;i<m;i++){
std::cin>>u[i]>>v[i];
du[u[i]]++; //计算每个点的度
du[v[i]]++;
}
for(int i=0;i<m;i++){
if(du[u[i]]>1&&du[v[i]]>1){ //计算每一条边有几种可能
sum+=(du[u[i]]-1)*(du[v[i]]-1)*2; //乘以2表示双向捷克
}
}
std::cout<<sum;
}
2.前缀和
就离谱听了这么多遍这词,原来这是基础啊,竟然还有名字,完全不知道这叫前缀和。看了一题前缀和的题之后,从前缀和到乘法逆元到快速幂到矩阵,用矩阵快速幂解决斐波那契数列那题挺好的,但是还没解决,就没法写题解了。
前缀和是指从位置1到当前位置的区间内所有数字的和,可以降低时间复杂度。在输入数据的同时计算前缀和,来一道二维前缀和。
模意义下的乘法逆元,线性求逆元,有用到费马小定理。
快速幂
<1>(洛谷P1226)
#include<iostream>
using namespace std;
int main() {
long long int a,b,p;
cin>>a>>b>>p;
cout<<a<<'^'<<b<<' '<<"mod"<<' '<<p<<'=';
long long int r=1;
a=a%p;
while(b>0){
if(b%2==1)r=r*a%p; //b&1==1 代表b与1按位与
a=a*a%p;
b=b/2; //b>>1 代表b的二进制向前一位
}
long long int t=r%p;
cout<<t;
return 0;
}
矩阵
<2>(CF Gym104101F)
当时一直在用优先队列,但是思想不到位,有案例一直没法通过,后来补题的时候也没想出来,去找别人要了代码的,应该用数学思维会更简单一点。
题解:
题目要求输入n,m,k三个数,再输入三个长度为n的序列初始生命值a,减少生命值速度b,恢复生命值c,要求在m分钟后使用k次恢复技能,使得最多的人活下来并输出个数。
虽然是英文题目但并不难理解,可以运用数学思维做这题,m分钟后减少的生命值减去初始生命值,为负则活着,为正或零则计算需要使用几次恢复技能才能复活,储存至数组f并排序,计算前缀和s,与k进行比较确定可以存活几个人。注意数组范围和数据大小,防备数组越界。
另外这题可以用贪心做,但是我的代码一直WA,就不清楚具体做法了。
代码:
#include<bits/stdc++.h>
using namespace std;
long long int a[1000000],b[1000000],c[1000000],f[1000000],s[10000000];
void solve()
{
int n,m,k;
cin>>n>>m>>k;
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<=n;i++)
cin>>b[i];
for(int i=1;i<=n;i++)
cin>>c[i];
for(int i=1;i<=n;i++)
{
f[i]=((b[i]*m)-a[i])/c[i]+1; //需要施展几次技能才能复活
f[i]=f[i]>0?f[i]:0; //不需要技能的直接赋值为0
}
sort(f+1,f+n+1);
for(int i=1;i<=n;i++)
{
s[i]=s[i-1]+f[i]; //前缀和
}
for(int i=1;i<=n;i++)
{
if(s[i]>k)
{
cout<<i-1<<endl; //不足以复活第i个人
return ;
}
}
cout<<n<<endl; //全部复活
return ;
}
signed main() {
ios::sync_with_stdio(false),cin.tie(NULL) , cout.tie(NULL);
solve();
return 0;
}
<3>(CF Gym103480B)
额不知道说什么好,一个挺简单的前缀和,区间竟然是有序的,补题的时刻看了代码才做出来的,必须加上开头一行不然后TLE
代码:
#include<bits/stdc++.h>
using namespace std;
signed main() {
ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
int t;
cin>>t;
while (t--){
int n;
cin>>n;
vector<int>q(n+1);
for (int i = 1; i <=n ; ++i) {
cin>>q[i];
q[i]=q[i-1]+q[i]; //前缀和
}
int ans=0;
for (int i = 1; i <=n ; ++i) {
int g=*lower_bound(q.begin(),q.end(),q[i-1]+7777);
if(g-q[i-1]==7777)ans++;
}
cout<<ans<<endl;
}
}
3.STL
好多STL好像都只是看过没有很注意学,统一拉一个模块,省的每次都要再学几个。最近做题做到过好几道了好像,每次都要想很久还要暴力解法,还是得学一下的。其实我真的以为我会STL了…二维数组队列栈之类的,没想到还有这些…
线性表主要是栈stack,队列queue,链表,基本的插入查找操作都很简单,但是以前基本都在用int数组,有意识地可以使用这些,熟悉相关操作,就不特意贴代码了,重点关注一下优先队列。以下三种数据结构均不可以通过下标访问,若要访问则需开vector。
<1> pair
跟queue,stack等的操作基本一致,俩个参数分别是first和second。
#include <iostream>
#include <utility>
#include <string>
#include <vector>
using namespace std;
int main() {
// pair<int,string> a={2,"abc"}; 第一种赋值方法
pair <int , string> b;
b=make_pair (4,"abcd");
cout<<b.first<<' '<<b.second<<endl;
//分别输出b的俩个对象
vector<pair<int, int > > zb;
//pair有利于利用vector输入若干个坐标
for(int i=0;i<4;i++){
int m,n;
cin>>m>>n;
zb.push_back(make_pair(m,n));
}
for(auto i:zb){ //这个地方循环条件总是写错,要注意
cout<<i.first<<" "<<i.second<<endl;
}
}
<2> set
set的底层逻辑是红黑树,unordered_set是哈希表。set类型存储的是俩个值,first是数字,second用来判定操作是否可行,一般情况均省略。
#include<iostream>
#include<set>
using namespace std;
int main() {
set<int>number={5,1,2,3};
number.insert(2); //set自动去重,插入失败
cout<<number.insert(4).second<<endl; //second是bool类型
cout<<number.insert(4).second<<endl; //插入失败输出0
cout<<number.size()<<endl; //判断元素个数
cout<<number.count(1)<<endl; //判断是否存在某数
number.erase(3); //删除某数字
for(auto i:number){
cout<<i<<' ';
}
return 0;
}
#include<iostream>
#include<unordered_set>
using namespace std;
int main() {
unordered_set<int>number; //自动去重不排序
}
<3> map
同上,补充一句,unordered_map蛮适合坐标系状态的,可以考虑取代二维数组和struct。数组是用数字类型的下标来索引元素的位置,而map是用字符型关键字来索引元素的位置。
#include<iostream>
#include<map>
#include<string>
#include<unordered_map>
using namespace std;
int main() {
map<string,int>x;
x.insert(make_pair("zhangsan",4));
x.insert(pair<string,int>("lisi",3));
for(auto i:x){ //根据字符串首字母进行排序
cout<<i.first<<' '<<i.second<<endl;
}
for(int i=0;i<4;i++){ //自动计算字符串的数量
string c;
cin>>c;
x[c]++;
}
x.erase("zhangsan"); //根据key删除数据
for(auto &pair:x){ //第二种遍历方法
cout<<pair.second<<endl;
}
return 0;
}
#include<iostream>
#include<utility>
#include<unordered_map>
using namespace std;
int main() {
unordered_map<pair(int,int),bool>x; //表示坐标状态
x.insert(make_pair(make_pair(3,4),true));
return 0;
}
4.双指针
<1>(CF Gym103480M)
额这题好像根本没有用到指针,主要是个思想吧,就从左右同时向中间走,练习赛看到这题的时候是打算用双指针做的,但又又又写不出来,后来补题一次过的,好像并不算很难
题解:
题目给出T行,要求调整出正确语序即从前后轮流取单词最后输出符号。
一个vector数组储存string后,op储存符号。利用l,r俩字母从前后轮流输出单词
代码:
#include<iostream>
#include<string>
#include<vector>
using namespace std;
int main() {
int t;
cin>>t;
while(t--){
vector<string>a; //储存字符串数组a
char op;
string str;
while(cin>>str){
if(str.back()=='.'||str.back()=='!'||str.back()=='?'){
op=str.back();
str.pop_back(); //去除字符op即符号
a.push_back(str); //读入字符串str
break;
}
a.push_back(str);
}
for(int l=0,r=a.size()-1;l<=r;l++,r--){
if(l==r){
cout<<a[l]; //防止输出俩次
}
else{
cout<<a[l]<<' '<<a[r];
if(r-l>1){
cout<<' '; //避免最后的符号与字符间有空格
}
}
}
cout<<op<<endl;
}
return 0;
}
三、总结
这次的代码都有注意在用std::,由于用了using namespace std;所以不写成这样也不会报错,但毕竟是c++,还是规范一下语言用法吧。有一个点,好多up主和题解都喜欢用const提前定义long long,学一下?有些题目是为了熟悉算法在写的,就没有再贴上来了,以后尽量精简一点还是。
补题不是每一题都写了题解,有些代码看起来挺浅显易懂的,当时确一点也想不出来,就没有写上去了。周一的蓝桥杯模拟赛有几题还没有补,周日有空可以再看一下,写在下周的周报里。下周学一下拓扑排序,Dijkstra,还有bfs上次也没学完。