SMU Winter 2024 div2 2nd


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上次也没学完。

  • 21
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值