码蹄集部分题目(2024OJ赛15期;前缀和+栈+堆+队列)

1🐋🐋🐋门票(钻石;前缀和)

时间限制:1秒

占用内存:128M

🐟题目描述

🐟输入输出格式

🐟样例

🐚样例

🐚备注

🐟题目思路

这道题目乍看就是简单的前缀和+判断区间值而已,但是暴力求解只能拿70分。满分题解是求逆序数+归并排序。至于为什么求逆序数,那是因为,如果该段内都满足条件,那前缀和的值应该是非减的,所以所有可能减去逆序数量就是我们要的答案了。

🌮归并排序知识补充

定义

归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。

算法思路

归并排序算法有两个基本的操作,一个是,也就是把原数组划分成两个子数组的过程。另一个是,它将两个有序数组合并成一个更大的有序数组。

  • 1、将待排序的线性表不断地切分成若干个子表,直到每个子表只包含一个元素,这时,可以认为只包含一个元素的子表是有序表。

  • 2、将子表两两合并,每合并一次,就会产生一个新的且更长的有序表,重复这一步骤,直到最后只剩下一个子表,这个子表就是排好序的线性表。

分治图解

排序图解

算法性能

速度仅次于快速排序。

时间复杂度

O(nlogn)

空间复杂度

O(N),归并排序需要一个与原数组相同长度的数组做辅助来排序。

稳定性

稳定

摘自:排序——归并排序(Merge sort)-CSDN博客

🐟代码

🥪70分暴力求解(超时)

#include<bits/stdc++.h> 
​
using namespace std;
int a[1000010],sum[1000010];
const int N=1e9+7;
int main( )
{
    int n,t;
    cin>>n>>t;
    long long count=0;
    for(int i=0;i<n;i++) cin>>a[i];
    for(int i=0;i<n;i++) sum[i]=sum[i-1]+a[i];
    for(int i=0;i<n;i++)
    {
        for(int j=i;j<n;j++)
        {
            int cn=j-i+1;
            if((sum[j]-sum[i-1])/cn>=t) count++;
        }
    }
    count=count%N;
    cout<<count<<endl;
    return 0;
}

🥪100分逆序数+归并排序

#include<bits/stdc++.h> 
​
using namespace std;
const int MOD=1e9+7;
const int N=1e6+10;
#define ll long long
ll n,t,a[N],sum[N],ans;//ans记录逆序子序列数量
ll q[N];//归并排序的结果数组
void merge_sort(int l,int r)
{
    if(l>=r) return;//递归停止条件
    //分
    int mid=(l+r)/2;
    merge_sort(l,mid);//划分成子任务并分别递归调用归并排序算法
    merge_sort(mid+1,r);
    //归并算法本体
    int i=l,j=mid+1,t=0;//i和j分别是左右两部分的指针,t是结果数组当前要放位置的下标
    while(i<=mid&&j<=r)
    {
        if(sum[i]>sum[j])//结果数组先放小的那个,也就是j那里的
        {
            q[t++]=sum[j++];
            ans+=mid-i+1;//对于每一部分来说,前边的分治任务都已经将这一部分排好序了,所以i~mid和j~r这两部分都已经是升序排列,如果i那里的比j这里的大,那么i+1~mid的一定比j这里的大,所以求逆序数直接减就可以了
            ans%=MOD;
        }
        else q[t++]=sum[i++];//否则就放i那里的,这时不计逆序数
    }
    //这时i或j某一边提前结束的情况,另一边直接全部移到结果数组
    while(i<=mid) q[t++]=sum[i++];
    while(j<=r) q[t++]=sum[j++];
    for(i=l,j=0;i<=r;i++,j++) sum[i]=q[j];//最后更新一下sum数组,因为每次都是基于sum计算的
}
int main( )
{
    cin>>n>>t;
    long long count=0;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        a[i]-=t;//根据公式,平均值大于等于t,也就是求和大于等于nt,所以每次直接在a[i]中就减去t,这样只要区间和大于等于0就满足条件
        sum[i]=sum[i-1]+a[i];
    } 
    merge_sort(0,n);//归并排序
    cout<<(n*(n+1)/2-ans)%MOD<<endl;//所有可能子序列数减去ans数量并取模
    return 0;
}

2🐋🐋🐋逆波兰式(星耀;栈)

时间限制:1秒

占用内存:128M

🐟题目描述

🐟输入输出格式

🐟样例

🐚样例

🐚备注

🐟题目思路

这道题目还是用栈进行算式运算,只用两个栈就可以实现结果输出那种样子,一个是st存放结果字符串成员,一个是op临时存放操作符。数字直接放入st,操作符在遇到以下情况时要从op拿出放到st:

  • 当前为“)”,需要将op中的小括号前的所有操作符放入st,最后别忘了把“(”也pop掉

  • 当前运算符比op.top( )的优先级低或同级,要将op.top( )的放入st,目的是保证运算顺序,包括同级从左到右运算的顺序

到这里第一步就结束了,接下来就是正常的calculate函数运算了,多的就是遇到运算符时进行运算后需要cout出来当前运算结果情况

🐟代码

#include<bits/stdc++.h> 
#include<string>
using namespace std;
​
bool is_op(char c){ return c =='/'||c=='+'||c=='-'||c=='*';}
​
int priority(char op){
    if(op == '+'|| op == '-')  return 1;
    if(op == '*' || op == '/')  return 2;
    return -1;
}
void calculate(string s){
    stack<int> st;
    bool change = false;
    for(int i=0;i<s.length(); i++){
        if(is_op(s[i])){
            int r= st.top();
            st.pop();
            int l= st.top();
            st.pop();
            switch(s[i]){//这里不知道为什么改成if语句就报错
            case '+':
                st.push(l + r);
                break;
            case '-':
                st.push(l-r);
                break;
            case '*':
                st.push(l * r);
                break;
            case '/':
                st.push(l /r);
                break;
            }
            change = true;
        } else
            st.push(s[i]-'0'),change = false;
        if(change){
            stack<int> temp, temp2;
            temp = st;
            //反转顺序到temp2中
            while (!temp.empty())
                temp2.push(temp.top()), temp.pop();
            //对temp2中的成员进行输出
            while (!temp2.empty())
                cout << temp2.top()<<" ",temp2.pop();
            for(int j=i+1;j<s.length(); j++)
                cout << s[j] << " ";
            cout << endl;
        }
    }
}
​
string work(string s){ 
    stack<char> st;//结果
    stack<char> op;//操作符栈
    for(int i=0;i<s.size();i++){
        if(s[i]=='('){
            op.push('(');
        }else if(s[i]==')'){
            while (op.top()!='('){
                st.push(op.top());
                op.pop();
            }
            op.pop();
        }else if(is_op(s[i])){
            while(!op.empty() && priority(op.top())>= priority(s[i])){
                st.push(op.top());
                op.pop();
            }
            op.push(s[i]);
        } else {
            st.push(s[i]);
        }
    }
    while(!op.empty()){
        st.push(op.top());
        op.pop();
    }
    
    string ans;
    int len = st.size();
    while(len--){
        ans =st.top()+ ans;
        st.pop();
    }
    return ans;
}
​
int main( )
{
    string s;cin >> s;
    string s2=work(s);
    for(int i=0;i<s2.length(); i++)
        cout << s2[i] <<" ";
    cout << endl;
    calculate(s2);
    return 0;
}

3🐋🐋🐋新月轩就餐(钻石;双端队列)

时间限制:1秒

占用内存:128M

🐟题目描述

🐟输入输出格式

🐟样例

🐚样例

🐚备注

🐟题目思路

这道题目因为涉及到顺序不变问题,所以使用deque双端队列,目的是方便改变区间的首尾。

具体详见代码注释~

🌮deque vs priority_queue

这里主要对比的是双端队列deque和最大/小堆priority_queue,二者有以下不同:

  • 端口进出:deque双端都可进出;priorty_queue只有一端可进出

  • 排序:deque中的元素无法排序,只是queue加上一端可操作而已;priority_queue中的元素是要进行排序的,每次top的是最大/小值

  • 使用的函数:deque用的是front和back;priority_queue用的是top(这一点只是提醒一下,不要用错了)

🐟代码

#include<bits/stdc++.h> 
​
using namespace std;
const int N=1e6+7;
const int INF=0x3f3f3f;
deque<int> q;
int ans=INF;
int n,m,a[N],cnt[2001],l,r,type;
int main( )
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        if(!cnt[a[i]]) type++;//该类型还没有记录过,记一下
        cnt[a[i]]++;//标记为记录过了
        q.push_back(i);//当前位置入队列
        while(!q.empty()&&cnt[a[q.front()]]>1) //这是在判断,如果头上的元素在当前区间内还有,那么头上这个就可以去掉了,这样就能得到更小的区间了
        {
            cnt[a[q.front()]]--;
            q.pop_front();
        }
        if(type==m&&q.size()<ans) //所有类型都有了,并且比当前记录的最短区间长度还要短,就更新
        {
            ans=q.size();
            l=q.front();
            r=q.back();
        }
    }
    cout<<l<<" "<<r<<endl;
    return 0;
}

4🐋🐋🐋植发(星耀;堆)

时间限制:2秒

占用内存64M

🐟题目描述

🐟输入输出格式

🐟样例

🐟题目思路

这个重点在于怎么安排头发的放置顺序和放在哪里:

  • 先放寿命最小的点,因为寿命小的点所能到达的位置,寿命大的都能到达

  • 先放到离出发点最近的位置,这样可以给另一个出发点的头发更多的放置机会

🐟代码

#include<bits/stdc++.h> 
​
using namespace std;
const int N=1e4+10;
struct node
{
    int x,y;
    int dis;
    bool operator<(const node &a) const {return dis<a.dis;}
};
int a1[N],a2[N],n,m,k,l;//a1a2存储了不同寿命的头发都有多少根
bool used[N][N];
vector<pair<int,int> > s1[N],s2[N];//s1s2存储的是不同距离下有哪些点,s1是与(0,0)的距离,s2是与(0,m+1)的距离
priority_queue<node> down,up;
int distance(int x1,int y1,int x2,int y2) {return abs(x1-x2)+abs(y1-y2);}
int main( )
{
    cin>>n>>m;
    cin>>k;
    //获得(0,0)处的头发寿命
    for(int i=0;i<k;i++)
    {
        int cur;
        cin>>cur;
        a1[cur]++;//(0,0)处寿命为cur的头发数加一
    }
    cin>>l;
    //获得(0,m+1)处的头发寿命
    for(int i=0;i<l;i++)
    {
        int cur;
        cin>>cur;
        a2[cur]++;//(0,m+1)处寿命为cur的头发数加一
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)//记录每个距离都有哪些位置
        {
            s1[distance(i,j,0,0)].push_back({i,j});//记录与(0,0)的距离
            s2[distance(i,j,0,m+1)].push_back({i,j});//记录与(0,m+1)的距离
        }//如果有多个点和(0,0)或(0,m+1)距离相同,就会被都放进去
    }
    int flag=0;
    //先看与(0,0)的距离
    for(int i=1;i<=n+m;i++)//遍历所有n+m个距离
    {
        for(int j=0;j<s1[i].size();j++)//对于指定的距离,遍历满足这些距离的所有点的坐标,把符合的点放到队列里
        {
            int x=s1[i][j].first,y=s1[i][j].second;
            int tmp=distance(x,y,0,m+1);
            down.push({x,y,tmp});//因为使用的是最大堆,要想距离(0,0)最近,就是按距离(0,m+1)的距离排序得到与之的最大距离
        }
        for(int j=0;j<a1[i];j++)//来放(0,0)的每一根头发,遍历距离为i的所有头发
        {
            if(down.empty())//从(0,0)出发距离为i处没有能放的头发了
            {
                flag=1;
                break;
            }
            int tmpx=down.top().x,tmpy=down.top().y;//得到在(0,0)处拿到的与(0,m+1)距离最大的坐标
            down.pop();
            used[tmpx][tmpy]=1;//这个位置被放上头发了
        }
        if(flag==1) break;
    }
    //再看离(0,m+1)的距离,下边与上方同理
    for(int i=1;i<=n+m;i++)
    {
        for(int j=0;j<s2[i].size();j++)
        {
            int x=s2[i][j].first,y=s2[i][j].second;
            if(used[x][y]==1) continue;
            node tmp;
            tmp.dis=distance(x,y,0,0),tmp.x=x,tmp.y=y;
            up.push(tmp);
        }
        for(int j=0;j<a2[i];j++)
        {
            if(up.empty())
            {
                flag=1;
                break;
            }
            int tmpx=up.top().x,tmpy=up.top().y;
            up.pop();
            used[tmpx][tmpy]=1;
        }
        if(flag==1) break;
    }
    if(flag==0) cout<<"YES"<<endl;
    else cout<<"NO"<<endl;
    return 0;
}

5🐋🐋🐋第一节离数课后(星耀;栈)

时间限制:0.5秒

占用内存:64M

🐟题目描述

🐟输入输出格式

🐟样例

🐚样例

🐚备注

🐟题目思路

这道题目也是使用栈进行表达式运算。重点是检查出有无非法表达的情况。

非法表达情况:

  • 开头或结尾不能是and或not:以为她们是双目运算符,不能只有一个成员参与运算

  • not后边只能跟not或操作数

  • 我们将not及其后边的操作数看作一个整体,那么操作数和运算符应该是一一间隔的,如 a and b or c

🐟代码

#include <bits/stdc++.h>
using namespace std;
const int N = 267;
string s[N];
int cnt, s2[N];
​
bool is_op(string s) {return s=="or"||s=="and"||s=="not";}
​
int priority(string op)
{
    if(op=="or") return 1;
    if(op=="and") return 2;
    if(op=="not") return 3;
    return -1;
}
​
void calculate(stack<string> &st, string op)
{
    //取出两个操作数(没有两个就取一个)
    string l, r;
    r = st.top();
    st.pop();
    if (!st.empty()) l = st.top();
    //计算
    if (op == "not")
    {
        if (r == "true") st.push("false");//not true即为false
        else st.push("true");//not false即为true
    }
    if (op == "and")
    {
        st.pop();
        if (r == "true" && l == "true") st.push("true");
        else st.push("false");
    }
    if (op == "or")
    {
        st.pop();
        if (r == "false" && l == "false") st.push("false");
        else st.push("true");
    }
}
​
string work(string *s, int cnt)
{
    stack<string> st;//操作数栈
    stack<string> op;//运算符栈
    for (int i = 0; i < cnt; i++)
    {
        if (is_op(s[i]))
        {
            string cur = s[i];
            if (cur != "not")
                while (!op.empty() && priority(op.top()) >= priority(cur))
                {
                    calculate(st, op.top());
                    op.pop();
                }
            op.push(cur);//先把优先级高的计算完再放进去这个运算符
        }
        else//是操作数
        {
            st.push(s[i]);
            while (!op.empty() && op.top() == "not")
            {
                calculate(st, op.top());
                op.pop();
            }
        }
    }
    while (!op.empty())
    {
        calculate(st, op.top());
        op.pop();
    }
    return st.top();
}
​
bool check(string *s, int cnt)//检查是否error
{
    bool flag = true;
    if (s[0]=="or"||s[0]=="and"||s[cnt-1]=="or"||s[cnt-1]=="and"||s[cnt-1]=="not")//1首尾为双目运算符;2尾为not(即not后边没有跟操作数)。这些都是error的情况
    {
        flag = false;
        return flag;
    }
    for (int i = 0; i < cnt; i++)//剩下需要遍历所有的内容检查not的正确性
    {
        if (s[i] == "not" && (s[i + 1] == "or" || s[i + 1] == "and"))//not后边紧跟运算符
        {
            flag = false;
            return flag;
        }
    }
    int cnt2 = 0;
    for (int i = 0; i < cnt; i++)
    {
        if (s[i] == "or" || s[i] == "and")
            s2[cnt2++] = 1;
        else if (s[i] == "true" || s[i] == "false")
            s2[cnt2++] = 0;
        if (cnt2 >= 2 && (s2[cnt2 - 2] ^ 1) != s2[cnt2 - 1])//说明有两个运算符或操作数连在一起,如果是1010/0101,那么等式会相等
        {
            flag = false;
            return flag;
        }
    }
    return flag;
}
​
int main()
{
    while(cin >>s[cnt]) cnt++;
    if (!check(s,cnt))
    {
        cout << "error";
        return 0;
    }
    cout << work(s, cnt);
    return 0;
}

6🐋🐋🐋二阶前缀和(黄金;前缀和)

时间限制:1秒

占用内存:128M

🐟题目描述

🐟输入输出格式

🐟样例

🐚样例

🐚备注

🐟题目思路

二维矩阵前缀和的典型模板题,题目不难,只要看懂要干什么就好写很多,总结一下题目要点:

  • 整个过程类似卷积的过程,就是拿着一个边长为r的正方形覆盖过整个矩形的所有位置,每次移动的距离为1

具体解题过程详见代码注释~

🐟代码

#include<bits/stdc++.h> 
using namespace std;
const int N=1010;
int a[N][N], sum[N][N], ans;
​
int main( )
{
    // 录入数组内容的同时构建二维前缀和
    int n,r;
    cin>>n>>r;
    while(n--)
    {
        int x,y,w;
        cin>>x>>y>>w;
        x++,y++;
        a[x][y] += w;
    }
    // 计算二维前缀和
    for(int i=1;i<=1001;i++)
    {
        for(int j=1;j<=1001;j++)  sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];
        
    }        
    // 寻找指定范围的最大子阵,每次都是以(i,j)为右下角画正方形
    for(int i=r;i<=1001;i++)
    {
        for(int j=r;j<=1001;j++) ans=max(ans,sum[i][j]-sum[i-r][j]-sum[i][j-r]+sum[i-r][j-r]); 
    }
    // 输出结果 
    cout<<ans<<endl;
    
    return 0;
}

有问题我们随时评论区见~

⭐点赞收藏不迷路~

  • 61
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
循环比日程表是一种常见的算法问题,可以使用递归或迭代的方式来生成日程表。下面是一个使用C++编写的循环比日程表的示例代码: ```cpp #include <iostream> #include <vector> using namespace std; void generateSchedule(int teams) { if (teams % 2 != 0) { teams++; // 如果队伍数为奇数,添加一个虚拟队伍 } int rounds = teams - 1; // 总轮次数 int matches = teams / 2; // 每轮的比场次 vector<vector<int>> schedule(rounds, vector<int>(matches)); // 初始化第一轮的比安排 for (int i = 0; i < matches; i++) { schedule[0][i] = i + 1; } // 生成后续轮次的比安排 for (int round = 1; round < rounds; round++) { for (int match = 0; match < matches; match++) { int team1 = schedule[round - 1][match]; int team2; // 计算每个队伍的对手 if (match == 0) { team2 = teams - 1; } else { team2 = schedule[round - 1][match - 1]; } // 考虑虚拟队伍的情况 if (team1 == teams - 1 || team2 == teams - 1) { team1 = (team1 + 1) % (teams - 1); team2 = (team2 + 1) % (teams - 1); } schedule[round][match] = team2; } } // 打印比日程表 for (int round = 0; round < rounds; round++) { cout << "Round " << round + 1 << ": "; for (int match = 0; match < matches; match++) { cout << schedule[round][match] << " vs " << teams - schedule[round][match] - 1 << " "; } cout << endl; } } int main() { int teams; cout << "Enter the number of teams: "; cin >> teams; generateSchedule(teams); return 0; } ``` 这段代码中,我们首先根据输入的队伍数计算总轮次数和每轮的比场次。然后,使用一个二维向量 `schedule` 来存储比安排。我们从第一轮开始,逐轮生成比对阵,并将结果存储在 `schedule` 中。最后,打印出比日程表。 希望这个示例代码对你有帮助!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值