第5周题解

1.碰撞2

在 xy 坐标系中有 N 个人,第 i 个人的位置是 (Xi,Yi),并且每个人的位置都不同。

我们有一个由 LR 组成的长为 N 的字符串 S,Si== R 代表第 i 个人面向右,Si== L 代表第 i 个人面向左。

现在所有人开始朝着他们各自面向的方向走,即面向右 x 就增,面向左 x 就减。

例如,当 (X1,Y1)=(2,3),(X2,Y2)=(1,1),(X3,Y3)=(4,1),S=RRL,人们的移动如图。

在这里插入图片描述

我们把两个人对向行走到一个位置称为一次碰撞。请问如果人们可以无限走下去,会有人产生碰撞吗?

输入格式

第一行一个整数 N;

接下来 N 行,每行两个整数 Xi 和 Yi,表示第 i 个人的位置;

最后一行是一个由 LR 组成的长为 N 的字符串 S。

输出格式

如果会有碰撞,输出 Yes,否则输出 No

样例输入 1

3
2 3
1 1
4 1
RRL

样例输出 1

Yes

样例输入 2

2
1 1
2 1
RR

样例输出 2

No

样例输入 3

10
1 3
1 4
0 0
0 2
0 4
3 1
2 4
4 2
4 4
3 3
RLRRRLRLRR

样例输出 3

Yes

数据规模

所有数据保证 2≤N≤2×105,0≤Xi≤109,0≤Yi≤10^9

一道简单的模拟题。我们可以先想一想怎么才能产生碰撞:

1这两个人都必须在同一行里

2.这两人的方向必须相反

3.向右走的人的x坐标必须小于向左走的人的x坐标

那么知道了这三个条件,我们只需要判断每一行情况。我们只需要记录向右走的人的最大x坐标max_x和向左走的人的最小x坐标min_x,只需要maxx_x>min_x那就代表必定发生碰撞。

知道思路,代码就简单了。

完整代码如下:

#include<bits/stdc++.h>
using namespace std;
struct point{
    int x;
    int y;
    char d;
}parr[200005];   //人的坐标
bool cmp(const point &p1,const point &p2){
    return p1.y>p2.y;   //按纵坐标排序
}
int main(){
    int n;
    bool x1=0,x2=0;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>parr[i].x>>parr[i].y;
    }
    for(int i=1;i<=n;i++){
        cin>>parr[i].d;
    }
    sort(parr+1,parr+1+n,cmp);
    long long now=1,min_x,max_x,flag;
    while(now<=n){
     
        if(parr[now].d=='R'){
            min_x=parr[now].x,max_x=-1;  
   
        }        
        else{
           max_x=parr[now].x,min_x=9999999999;  //对第一人进行处理
          
        }
        if(now<=n){
            flag=parr[now].y;            //标记这一行
        }
        while(parr[now].y==flag&&now<=n){          
            if(parr[now].d=='R'){
                if(parr[now].x<min_x){
                    min_x=parr[now].x;
                     //更新min_x
                }
            }
            else{
                if(parr[now].x>max_x){
                    max_x=parr[now].x;
                    //更新max_x
                }
            }           
             now++;   
        }        
        if(min_x<max_x){
            cout<<"Yes";
            return 0;
        }
        
    }
    cout<<"No";
    return 0;
}

2. 优美!最长上升子序列

多组数据。

每组将给定一个数组。派派希望从中选择一个递增的子序列,越长越好。

但派派认为,这样选出来的子序列依然不够「优美」,形式化的讲,派派希望选择的下标(从 1 开始)需要满足

i1∣i2∣i3∣⋯∣ik

其中 a|b 表示整除, 即 a 是 b 的约数。

请你帮助派派完成任务吧!

注:子序列的含义不再赘述。

输入格式

第一行一个整数 T,表示接下来有 T 组数据。

每组数据包含两行,第一行包含一个整数 N。

随后一行,包含 N 个整数,表示原数组 {A}

输出格式

对于每组数据,输出一行,包含一个数,表示能选出的「优美」的最长上升子序列长度。

数据规模

  • 1≤T≤100
  • 1≤N≤10^6,但保证 ∑i=1TNi≤10^6
  • 1≤Ai≤10^9

样例输入

4
4
1 4 6 7
2
2 2
10
1 2 3 4 5 6 7 8 9 10
10
10 9 8 6 5 2 3 1 2 1

样例输出

3
1
4
1

解释:

对于第一组数据,能选择的「优美」最长上升子序列为 {A1,A2,A4}={1,4,7}

对于第三组数组,选择 {A1,A2,A4,A8}={1,2,4,8}

对于第四组数据,可选择的「优美」最长上升子序列长度为 1.

我们可以想想原先我们做最长上升子数列时是怎么个做法,我们运用的是动态规划的思想,设 f(i) 表示为到A_i为止的最长上升子数列的长度。

动态转移方程为:
f ( i ) = m a x ( f ( i ) , f ( k ) + 1 )   k < i , a i > a k f(i)=max(f(i),f(k)+1) \ k<i ,a_i>a_k f(i)=max(f(i),f(k)+1) k<i,ai>ak
那么我们可以套这个思路,只需要在限制条件上加个: i|k 就行了。

结果真的这么简单吗?那当然不行,(~~因为我就TLE了)~~显然这里的数据规模太大了。我们需要枚举的因子数太多了,显然时间复杂度就太大了。

那么我们就需要亿一点点小技巧。

我们可以通过枚举倍数得方法来进行dp,这样最后的时间复杂度就可以通过了。

说出来你可能听不懂,那就直接看代码吧:

#include<bits/stdc++.h>
using namespace std;
#define N 1000005
int num[N],dp[N];  
int main(){
    int n,t;
    cin>>t;
    for(int a=0;a<t;a++){
        cin>>n;
        for(int i=1;i<=n;i++){
            cin>>num[i];
        }
        for(int i=1;i<=n;i++){
        	dp[i]=1;   //初始化
		}
        for(int i=1;i<=n;i++){  //i表示倍数
            for(int j=2*i;j<=n;j+=i){    //j每次都在累加倍数
                if(num[j]>num[i]){
                    dp[j]=max(dp[j],dp[i]+1);  //状态转移
                }
            }
        }
        int max=-1;
        for(int i=1;i<=n;i++){
            if(dp[i]>max){
                max=dp[i];  //找最大
            }
        }
        cout<<max<<"\n";
    }
    return 0;
}

3.巨大的牛棚


农夫约翰想要在他的正方形农场上建造一座正方形大牛棚。他讨厌在他的农场中砍树,想找一个能够让他在空旷无树的地方修建牛棚的地方。我们假定,他的农场划分成 n * n的方格。输入数据中包括有树的方格的列表。你的任务是计算并输出,在他的农场中,不需要砍树却能够修建的最大正方形牛棚。牛棚的边必须和水平轴或者垂直轴平行。 考虑下面的方格,它表示农夫约翰的农场,‘.'表示没有树的方格,‘#'表示有树的方格

........
.#...#..
........
........
........
..#.....
........
........

那么最大的牛棚是5*5的。

输入描述

第一行输入一个正整数 n(1≤n≤1000)代表农场的大小,一个正整数T(1≤T≤n∗n), 接下来 T 行,每行2个整数,代表有树的格子的横纵坐标,保证任意两个树格子不相同

输出描述

输出一个正整数代表牛棚的最大边长

样例输入

8 3
2 2
2 6
6 3

样例输出

5

我一开始对这道题的唯一思路就是确定正方形的两点,然后对这个范围内进行判断,看有没有树。但显然,这么做的时间复杂度简直逆天。所以我就果断放弃这种做法。

然后就有了这道题的正确思想方法:动态规划

我们可以假设以一个点为左上角,他所做的正方形边长最长为 f(x)(y) ,那么状态转移方程应为:
f ( x , y ) = m i n ( f ( x + 1 , y ) , f ( x + 1 , y + 1 ) , f ( x , y + 1 ) ) + 1 , p l a c e ( x , y ) = 0 f(x,y)=min(f(x+1,y),f(x+1,y+1),f(x,y+1))+1,place(x,y)=0 f(x,y)=min(f(x+1,y),f(x+1,y+1),f(x,y+1))+1place(x,y)=0
为什么是这样的呢?我们可以想象一下,这里的 f(x+1,y),f(x+1,y+1),f(x,y+1)其实也就表示这个点的右边,下边和右下边的三个点,我们取它的最小值是因为只要有一个点被树给挡住了,那么显然,我们要求的这个点所做的正方形就不可能再增大了。为什么加1是因为这个点自身也算一个长度。place(x,y)=0代表这个点是没有树的。

那么我们的初始状态也就是所有点的 f(x,y) 都为0.

完整代码如下:

#include<bits/stdc++.h>
using namespace std;
int book[1005][1005],dp[1005][1005];
int main(){
    int n,t,x,y,ans=0;
    cin>>n>>t;
    for(int i=1;i<=t;i++){
        cin>>x>>y;
        book[x][y]=1;   //标记
    }
    for(int i=1;i<=n;i++){
        if(!book[i][n]){
            dp[i][n]=1;
        }
        if(!book[n][i]){
            dp[n][i]=1;
        }
    }
    for(int i=n-1;i>=1;i--){
        for(int j=n-1;j>=1;j--){
            if(!book[i][j]){
                dp[i][j]=min(dp[i][j+1],dp[i+1][j]);
                dp[i][j]=min(dp[i][j],dp[i+1][j+1])+1;  //状态转移
                if(dp[i][j]>ans){
                    ans=dp[i][j];   //找最大值
                }
            }
        }
    }
    cout<<ans;
    return 0;
}

4.高利贷

19岁的大学生小 L 家里情况不好,每月固定生活费不足以买一部苹果手机。当他得知有贷款机构可以贷小额贷款并且可以分期等额还款,每月只需要还几百块时,在虚荣心的驱使下他开始自己的贷款之旅。

贷款机构声称利率按月累计,贷款人在贷款期间每月偿还固定的分期付款金额。

给出小 L 贷款的原值为 n,分期付款金额 m 和分期付款还清贷款所需的总月数 k,求该贷款的月利率 p。

输入格式

三个用空格隔开的正整数 n,m,k,含义如上述所示。

输出格式

一个实数 p,表示该贷款的月利率(用小数表示),和标准输出绝对值不超过10^−6即可。

数据范围

1≤n,m≤10^6,1≤k≤300

0≤p≤5

n≤m×k

输入样例1

1000 1200 1

输出样例1

0.200000

输入样例2

1000 100 12

输出样例2

0.029229

样例解释

对于第一组样例,小 L 贷款了 1000 元,并在一个月后支付 1200元还清了贷款。因此计算月利率 p 的公式为 1000×(1+p)=1200 或 1200/(1+p)=1000,求出 p=0.200000

对于第二组样例,小 L 贷款了 1000 元,并以每月支付 100 元的方式在 12 个月后还清了贷款。由于月利率的存在,他第 k 个月偿还的金额只相当于 100/(1+p)^k 元的初始金额,且这12个月共偿还初始金额1000元,求出 p=0.029229。

一道数学问题,我们如果想直接求出p是不好求的,但是我们发现,p越大,小L要付的钱就越多,当p越小时,小L要付的钱就越少。那么答案具有单调性,我们就可以用二分答案的方法判断答案是否正确来求得答案了。

对于judge()函数,我就不讲了,相信各位学过高中数学,或者有一点常识的都应该知道怎么写吧。(实在不行就找度娘吧)

还有就是要注意误差了。

完整代码如下:

#include<bits/stdc++.h>
using namespace std;
double n,m,k;
#define wucha 0.000001
bool judge(double x){
    double ans=0;
    for(int i=1;i<=k;i++){
        ans+=(m/pow(1.0+x,i));
    }
    if(abs(n-ans)<1e-6){
        return true;
    }
    return false;
}
int main(){
    cin>>n>>m>>k;
    double l=0,r=5;
    while(l<r-wucha){
        double mid=(l+r)/2.0;
        double ans=0;
        for(int i=1;i<=k;i++){
            ans+=(m/pow(1.0+mid,i));
        }
        if(n<ans&&abs(ans-n)>wucha){
            l=mid;
        }
        else if(n>ans&&abs(n-ans)>wucha){
            r=mid;
        }
        else if(abs(n-ans)<wucha){
            break;
        }
    }
    cout<<fixed<<setprecision(6)<<r;  //记得控制精度
    return 0;
}

5.背包

cc有一个背包,背包的体积为w,有n个物品,每一个物品的体积为a_i

cc希望将其中的一些物品放入他的背包中,他希望这些物品的体积之和至少是背包体积的一半,并且不超过背包的体积,即 ⌈w/2⌉≤sum≤w

请你帮cc判断这些物品中有没有符合条件的物品组合,如果有输出"YES", 没有输出"NO"

cc至少会拿一个物品

输入格式

第一行给出测试样例个数T

对于每一个样例:

第一行给出一个n和一个w,n个物品,背包的总体积是w

第二行给出一个序列a_1,…a_n,代表每一个物品的体积

输出格式

如果有请输出"YES", 没有输出"NO"

数据范围

1≤t≤1e4

1≤∑n≤2e5,1≤w≤1e18

0≤wi≤1e9

样例输入1

3
1 3
3
6 2
19 8 19 69 9 4
7 12
1 1 1 17 1 1 1

样例输出

YES
NO
YES

这道题其实就是一道数学问题。我们要想背包只装到半满,我们可以先将这些物品按照重量进行一个排序。那么我们要找的就是重量为w/2的这个特殊点。我们可以根据二分的方法来找第一个大于等于w/2重量的物品

1.如果没找到,那么我们就知道这些物品全部都是小于w/2的,那么我们只需从小到大不断累加,判断这些物品的总和有没有超过w/2,如果有的话,那就可以。

2.如果找到了,我们先判断该物品的重量是否就在w/2和w之间,如果在的话,那么就说明是可以的,如果是大于w的,那么就说明其他物品的重量全都小于w/2,只需累加判断即可。

知道思路后,就可以写出代码了(但是我当时就犯傻了,多找了第一个小于等于w的物品,简直多此一举,希望不会影响观看qwq):

#include<bits/stdc++.h>
using namespace std;
long long num[200005],n;
double w;
int search(){
    int l=0,r=n+1;
    while(l!=r-1){
        int mid=(l+r)/2;
        if(num[mid]>w){
            r=mid;
        }
        else{
            l=mid;
        }
    }
    if(num[l]<=w&&l>=1){
        return l;
    }
    else if(num[r]<=w&&w<=n){
        return r;
    }
    return 0;
}
bool judge(int l,int r){
    unsigned long long ans=0;
    if(num[l]>=ceil(w/2)&&num[l]<=w&&l>=1){
        return true;
    }
    else if(num[r]>=ceil(w/2)&&num[r]<=w&&r<=n){
        return true;
    }
    if(num[l]>w||l==n+1){
        if(r){
            for(int i=1;i<=r;i++){
                ans+=num[i];
                if(ans>=ceil(w/2.0)&&ans<=w){
                    return true;
                }
            }
        }
        else{
            return false;
        }
    }
    ans=0;
    if(num[r]<ceil(w/2)){
        for(int i=1;i<=r;i++){
            ans+=num[i];
            if(ans>=ceil(w/2.0)&&ans<=w){
                return true;
            }
        }
        return false;
    }
    if(!r){
        return false;
    }
    return false;
}
int main(){
    int t,l,r;
    cin>>t;
    for(int i=1;i<=t;i++){
        cin>>n>>w;    
        for(int a=1;a<=n;a++){
            cin>>num[a];
        }
        sort(num+1,num+1+n);
        l=lower_bound(num+1,num+1+n,(ceil)(w/2))-num;
        r=search();
        if(judge(l,r)){
            cout<<"YES"<<"\n";
        }
        else{
            cout<<"NO"<<"\n";
        }
    }
    return 0;
}

6.三回文序列

给定一个长度为n的序列a。

我们定义三回文序列是形如a…ak1…⏟1b…bk2…⏟2a…ak1…⏟1的序列,例如:[1,1,1,2,2,1,1,1]是一个三回文序列,而[1,1,1,2,2,2,3,3,3],[1,1,2,2,1]都不是三回文序列。

现在,希望你找到序列a中最长的三回文序列的长度。

注意,k1,k21,2可以为00

输入格式

第一行给出一个数字T,表示T组测试用例

对于每一个测试用例

第一行给出序列的长度n

第二行给出序列a1,a2,a3…an

输出格式

对于每一个测试用例,输出最长的三回文序列的长度。

数据范围

1≤t≤2000

1≤∑n≤2e^5,1≤ai≤26

样例输入

6
8
1 1 2 2 3 2 1 1
3
1 3 3
4
1 10 10 1
1
26
2
2 1
3
1 1 1

样例输出

7
2
4
1
1
3

这道题我就引用网上的题解来解释一下吧。(其实就是自己不会qwq)我们要找最长的序列,那么我们可以枚举两边的数字的长度,因为数字的大小范围为1~26,所以我们枚举26个数字,并且枚举长度。在找完左右两边的数字后,我们就来找剩下部分的最长的数字长度,这个操作都是可以通过预处理的方式找出每个区间的每个数字的多少。

那么我们取其中最大的那个长度输出即可。

说的可能有亿点点抽象,所以直接看代码吧:

#include<bits/stdc++.h>
using namespace std;
int num[200005];
int sum_num[30][200005];  //前缀和
int main(){
    int t,n,count=0,maxx=0,num1,num2,maxnum=1;
    cin>>t;
    for(int i=1;i<=t;i++){
        cin>>n;
        for(int j=1;j<=n;j++){
            cin>>num[j];
        }
        memset(sum_num,0,sizeof(sum_num));  
        sum_num[num[1]][1]=1;
        for(int a=2;a<=n;a++){
            for(int p=1;p<=26;p++){
                if(p==num[a]){
                    sum_num[p][a]=sum_num[p][a-1]+1;
                }
                else{
                    sum_num[p][a]=sum_num[p][a-1];  //预处理
                }
            }
        }
        int maxx=0;
        for(int a1=1;a1<=26;a1++){
            int l=0,r=n+1,num1=0,num2=0;
            for(int k1=1;k1<=n;k1++){
                while(num1<k1&&l<r){
                    l++;
                    num1+=(num[l]==a1);   //枚举左边数量
                }
                while(num2<k1&&l<r){
                    r--;
                    num2+=(num[r]==a1);   //枚举右边数量
                }
                if(num1!=k1||num2!=k1){
                    break;   //数量不同,退出
                }         
                for(int b=1;b<=26;b++){
                    if(l<r){
                        maxx=max(maxx,k1*2+sum_num[b][r-1]-sum_num[b][l]);   //记录最大值
                    }
                    else{
                        maxx=max(maxx,k1*2-1);   
                    }
                }       
            }
        }
        cout<<maxx<<endl;
    }
    return 0;
}

7.简单的异或问题

有一组整数 {0,1,2,…,2^m−1}, 请从中选出 k 个数,使得这 k 个数的异或和为 n, 请输出最大的满足条件的 k。

输入格式

两个数 n 和 m, 其中 0≤n≤2^m−1,1≤m≤60

输出格式

输出最大的满足条件的 k。

样例输入

2 2

样例输出

3

样例解释

对于样例,我们可以选择 {0,1,3}.

这道题有一个非常巧妙的思路,(但是我不会),就是,我们发现这些整数的异或和最终结果是0,那么我们根据异或的运算法则,我们只需对要求的数中对余的1进行增减即可。那么我们最终会发现,最终增减的数其实就是这个数本身,那么我们的最大结果其实也就是2^m-1了

完整代码如下:

#include<iostream>
 
using namespace std;
 
long long cj(long long m)
{
    long long ans=1;
    for(int i=1;i<=m;i++)   ans=ans<<1;
    return ans;   //求次方
}
 
int main()
{
        long long n,m;
        cin >>n >>m;
        if(n!=0&&m!=1)  cout <<(cj(m)-1) <<endl;
        else if(n==1&&m==1) cout <<2 <<endl;
        else if(n==0&&m==1) cout <<1 <<endl;  //特判
        else cout <<cj(m) <<endl;   
    return 0;
}

8.子串的循环挪动

给出一个字符串 s,你需要执行 m 个任务。每个任务给出两个下标 l_i,r_i, 和一个整数 k_i(字符串的下标从 1 开始),表示你需要循环挪动 s 的子串 s[l_i…r_i] k_i 次。请从前到后依次执行给出的每个任务。

字符串的循环挪动操作:将最后一个字符移到第一个字符的位置,并且将其他所有字符向右移一个位置。

比如:如果字符串 s 是 abacaba,一个任务为 l_1=3,r_1=6,k_1=1,那么答案为 abbacaa。接下来一个任务为 l_2=1,r_2=4,k_2=2,那么我们会得到 baabcaa

输入格式

第一行一个字符串 s,该字符串只包含小写英文字符。

第二行一个整数 m,表示任务个数。

接下来 m 行每行有三个整数 l_i,r_i, 和 k_i。

输出格式

输出执行了 m 个任务后的最终的字符串 s。

样例输入

abacaba
2
3 6 1
1 4 2

样例输出

baabcaa

数据规模

对于所有数据保证,1≤|s|≤10000(|s| 表示字符串 s 的长度),1≤m≤300,1≤l_i≤r_i≤|s|,1≤k_i≤1000000

这道题就是一道简单的模拟题,并且数据也挺小的,直接模拟就可以了,没什么好说的了。

完整代码如下:

#include<bits/stdc++.h>
using namespace std;
string a;
void turn(int l,int r,int k){
	char numarr[10005];
	int len=0;
	for(int i=r-k+1;i<=r;i++){
		numarr[len++]=a[i];
	} 
	for(int i=r;i>=l+k;i--){
		a[i]=a[i-k];
	}
	len=0;
	for(int j=l;j<l+k;j++){
		a[j]=numarr[len++];
	} 
}
int main(){
	int m,l,r,k,num;
	cin>>a>>m;
	for(int i=1;i<=m;i++){
		cin>>l>>r>>k;
		num=k%(r-l+1);
		turn(l-1,r-1,num);
	}
	cout<<a;
    return 0;
}

9.弗拉德和糖果 II

不久前,弗拉德过生日,他收到了一包糖果。有 n 种糖果,第 i 种糖果有 a_i 个(1≤i≤n)。

弗拉德决定每次只吃一个糖果。为了从吃东西中获得最大的乐趣,弗拉德不想连续吃两个相同类型的糖果。

帮助他弄清楚他是否可以在不连续吃两个相同的糖果的情况下吃掉所有的糖果。

简而言之,给定 n 个正整数 a_i,a_i 表示有 a_i 个 i,找到是否存在一种序列,使得所有的数都用上,且不存在 i 连续的情况

输入格式:

第一行,包含一个整数 n。 第二行,包含 n 个正整数。

输出格式:

输出一行,如果存在,输出YES,否则输出NO

样例输入

2
1 1

样例输出

YES

说明

只有两种情况:

1 2
2 1

无论先吃哪种糖果,都能吃完且不连续吃相同类型的糖果

数据限制

对于 100% 的数据,保证 1≤n≤5000000,1≤a_i≤2^30

我们显然只需要考虑出现次数最大的那种糖果,设这种糖果的个数为k,那么其他糖果的个数至少要为k-1个,否则,就肯定会有两个相邻的糖果。那么弱国其他糖果个数大于k-1怎么办,我们其实可以想,既然我们已经可以让最难不相邻的糖果都不相邻了,那么我其他的糖果其实也必定不会相邻,我们只需要按照不同的糖果来摆就可以了。

那么思路就很简单,我们记录最大数量的糖果个数,判断其他糖果的个数相加是否大于等于k-1即可了。

完整代码如下:

#include<iostream>
using namespace std;
long long num[5000005];
int main(){
    int n,maxx=0;
    long long sum=0;
    cin>>n;
    for(int i=0;i<n;i++){
        scanf("%lld",&num[i]);
        sum+=num[i];
        if(num[i]>maxx){
            maxx=num[i];
        }
    }
    sum-=maxx;
    if(maxx-1<=sum){
        cout<<"YES";
    }
    else{
        cout<<"NO";
    }
    return 0;
}

10.上帝的集合


现在上帝有一个空集合,现在他命令你为他执行下列三种操作 n 次,他每次会给你一个操作类型 op。

操作1:向集合中插入一个整数 x;

操作2:将集合中所有的数加上 x;

操作3:输出集合中最小的数,并从集合中将他删除,如果存在多个最小的整数,任意选择一个即可;

输入描述

第一行输入一个整数 n;

接下来的 n 行,每行的输入如下所示。第一个数代表 op,如果 op=1 或 op=2,第二个数代表 x_i:

1 x_i

2 x_i

3

输出描述

如果 op=3,请输出集合中的最小值。

样例输入

7
1 2
1 1
3
1 3
2 5
3
3

样例输出

1
7
8

数据范围

2≤n≤10^6, 1≤xi≤10^12

一道十分经典的模拟题。我们可以利用一个小根堆来轻松模拟3操作。

对于1操作,我们就直接将这个数加入堆中即可。而对于2操作,我们只需要增加一个标签tag,来标记现在已经加了多少x_i。

那么我们在进行1操作时,加入小根堆的其实也就是 x_i-tag 了,而进行3操作时,输出的也就是 x_i+tag 了。

那么这就是完整的思路了,完整代码如下:

#include<bits/stdc++.h>
using namespace std;
priority_queue<long long,vector<long long>,greater<long long> >p;  //创建小根堆
int main(){
    long long n,op,x,tag=0;
    scanf("%lld",&n);
    for(int i=1;i<=n;i++){
        scanf("%lld",&op);
        if(op==1){
            scanf("%lld",&x);
            p.push(x-tag);
        }
        else if(op==2){
            scanf("%lld",&x);
            tag+=x;
        }
        else{
            long long temp=p.top()+tag;
            printf("%lld\n",temp);
            p.pop();
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
06-01
这道题是一道典型的费用限制最短路题目,可以使用 Dijkstra 算法或者 SPFA 算法来解决。 具体思路如下: 1. 首先,我们需要读入输入数据。输入数据中包含了道路的数量、起点和终点,以及每条道路的起点、终点、长度和限制费用。 2. 接着,我们需要使用邻接表或邻接矩阵来存储图的信息。对于每条道路,我们可以将其起点和终点作为一个有向边的起点和终点,长度作为边权,限制费用作为边权的上界。 3. 然后,我们可以使用 Dijkstra 算法或 SPFA 算法求解从起点到终点的最短路径。在这个过程中,我们需要记录到每个点的最小费用和最小长度,以及更新每条边的最小费用和最小长度。 4. 最后,我们输出从起点到终点的最短路径长度即可。 需要注意的是,在使用 Dijkstra 算法或 SPFA 算法时,需要对每个点的最小费用和最小长度进行松弛操作。具体来说,当我们从一个点 u 经过一条边 (u,v) 到达另一个点 v 时,如果新的费用和长度比原来的小,则需要更新到达 v 的最小费用和最小长度,并将 v 加入到优先队列(Dijkstra 算法)或队列(SPFA 算法)中。 此外,还需要注意处理边权为 0 或负数的情况,以及处理无法到达终点的情况。 代码实现可以参考以下样例代码: ```c++ #include <cstdio> #include <cstring> #include <queue> #include <vector> using namespace std; const int MAXN = 1005, MAXM = 20005, INF = 0x3f3f3f3f; int n, m, s, t, cnt; int head[MAXN], dis[MAXN], vis[MAXN]; struct Edge { int v, w, c, nxt; } e[MAXM]; void addEdge(int u, int v, int w, int c) { e[++cnt].v = v, e[cnt].w = w, e[cnt].c = c, e[cnt].nxt = head[u], head[u] = cnt; } void dijkstra() { priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q; memset(dis, 0x3f, sizeof(dis)); memset(vis, 0, sizeof(vis)); dis[s] = 0; q.push(make_pair(0, s)); while (!q.empty()) { int u = q.top().second; q.pop(); if (vis[u]) continue; vis[u] = 1; for (int i = head[u]; i != -1; i = e[i].nxt) { int v = e[i].v, w = e[i].w, c = e[i].c; if (dis[u] + w < dis[v] && c >= dis[u] + w) { dis[v] = dis[u] + w; q.push(make_pair(dis[v], v)); } } } } int main() { memset(head, -1, sizeof(head)); scanf("%d %d %d %d", &n, &m, &s, &t); for (int i = 1; i <= m; i++) { int u, v, w, c; scanf("%d %d %d %d", &u, &v, &w, &c); addEdge(u, v, w, c); addEdge(v, u, w, c); } dijkstra(); if (dis[t] == INF) printf("-1\n"); else printf("%d\n", dis[t]); return 0; } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值