2023 CSP-J题解

T1 小苹果

题目描述

理论分析

        对于第一问,我们按照题意模拟每天取走的是多少个苹果即可。由于每天可以取走原来的\frac{1}{3},数据范围没次会降低到\frac{2n}{3},也就是说这样的过程的时间复杂度可以用下式表示:

f(n)=1+f(\frac{2n}{3})=2+f(\frac{2}{3}^2 n)=\cdots <2 \log_{2}n

对于本题的数据范围n<1e9,这个时间复杂度计算后在1e5左右,是可以接受的。

        对于第二问,首先有一个很显然的结论是:第n个苹果会存在于从第一天开始的连续的若干天,然后在后面的天里不再存在。因此,我们可以维护一个bool类型的变量,作为最后一个苹果还在不在的判别。然后在每一天的判断过程中,我们通过整除关系可判别,这个苹果是不是被拿走。

代码实现
#include <bits/stdc++.h>
using namespace std;

int main(){
    int n;
    cin>>n;
    int day=0, last=0;
    bool flag=false;
    while(n){
        day++;
        //是不是这天拿最后一个
        if(!flag&&(n-1)%3==0) flag=true, last=day;
        //这天结束,剩的苹果数
        n=n-(n-1)/3-1;
    }
    cout<<day<<" "<<last<<endl;

    return 0;
}

T2 公路

题目描述

理论分析

        贪心即可!对于这个题目来说,一共要走的路程是一定的,所以一共加多少油也是确定的。不存在选择某种加油方案剩余油多,而选择另外的方案剩余油少的问题。因此,我们要做的就是让每升油的油价尽可能的低。那么问题来了,我们能不能都用最低油价买油呢?显然,当开始的0号位置油价最低时(题目中的1号,这里直接对应代码的编号说明了!下同),我们可以办到这件事,但是当1号节点油价并不是最低价的时候,我们需要首先走到油价最低的站点,那就至少需要在前面的节点买油。也就是说,假设pos_{1}处的油价最低,我们的问题就演变为了,首先求1号点到pos_{1}最低花多少钱,然后后半段直接用pos_{1}处的油价走完就行。那前半程的求解是一个数据范围比原问题更小的同类问题的求解。

        我们当然可以像上述那样做递归的求解程序,只不过这样的时间效率是不高的,最差会来到O(n^{2}),只能通过一半左右的样例。实际上,上述贪心的过程等价于下面的贪心:我们从前往后考虑每个节点,维护需求加油的数量(只保证可以走到下一个节点)num,加完油后走到下个节点后会剩下多少油last,以及历史(已经过节点)的最低油价mn。对于每个节点,我们花费num*mn保证可以走到下一个节点。(也就是说,我们在拥有历史最低油价的那个点在原来的基础上多加num升油保证可以走到下一个点)。

代码实现
#include <bits/stdc++.h>
#define int long long
using namespace std;

signed main(){
    int n, d;
    cin>>n>>d;
    int last=0;
    vector<int> v(n);
    vector<int> a(n);
    for(int i=0; i<n-1; i++) cin>>v[i];
    int ans=0, mn=0x3f3f3f3f, num;
    for(int i=0; i<n-1; i++){
        cin>>a[i];
        // 历史低价
        mn=min(mn, a[i]);
        // 至少需要多少才能走到下一个点
        num=(v[i]-last+d-1)/d;
        ans=ans+num*mn;
        // 油余量够走多远
        last=num*d-v[i]+last;
    }
    cout<<ans<<endl;

    return 0;
}

T3 一元二次方程

题目描述

理论分析

        按照题意 老老实实模拟即可。需要注意的细节稍微有点多,但是细心一点也是可以一遍AC的!!
细节一:分数输出需要保证分母不是负数。
细节二:对于有两个解的情况而言,题中已经提示过x=q_{1}+q_{2}\sqrt r中的q_{2}>0,可以利用这一点简化代码的书写。
细节三:根号的化简要从大到小试探因数,这样更容易保证不重不漏。
细节四:根号内如果是完全平方数,那么最终结果将会不带有 “sqrt()” ,此时答案的化简需要自己分析好。
细节五:x=q_{1}+q_{2}\sqrt r格式的答案,要注意格外第一项是不是0的判断。
        大概就是这些细节吧。。。时间复杂度O(TM)

代码实现
#include <bits/stdc++.h>
#define int long long
using namespace std;

void print_sqrt(int s, int b, int a){
    int g=__gcd(b, a);
    pair<int, int> p;
    p.first=b/g;
    p.second=a/g;
    if(p.first<0) p.first*=-1;
    if(p.second<0) p.second*=-1;
    if(p.first!=1) cout<<p.first<<"*";
    cout<<"sqrt("<<s<<")";
    if(p.second!=1) cout<<"/"<<p.second;
}

void print_int(int b, int a){
    int g=__gcd(b, a);
    pair<int, int> p;
    p.first=b/g;
    p.second=a/g;
    if(p.second<0){
        p.first*=-1;
        p.second*=-1;
    }
    if(p.second==1) cout<<p.first;
    else cout<<p.first<<"/"<<p.second;
}

void solve(){
    int a, b, c, g;
    cin>>a>>b>>c;
    int delta=b*b-4*a*c;
    if(delta<0){
        cout<<"NO\n";
    }
    else if(delta==0){
        if(b==0) cout<<"0\n";
        else{
            print_int(-b, 2*a);
            cout<<"\n";
        }
    }
    else {
        bool flag=false;
        int num=1;
        for(int i=min(1000ll, delta-1); i>0; i--){
            if(i*i==delta){
                delta/=(i*i);
                num*=i;
                break;
            }
            else if(i*i<delta && delta%(i*i)==0){
                num*=i;
                delta/=(i*i);
            }
        }
        if(delta==1){
            if(a<0) print_int(-b-num, 2*a);
            else print_int(-b+num, 2*a);
            cout<<"\n";
        }
        else {
            if(b!=0) {
                print_int(-b, 2*a);
                cout<<"+";
            }
            print_sqrt(delta, num, 2*a);
            cout<<"\n";
        }
    }
}

signed main(){
    int n, m;
    cin>>n>>m;
    while(n--){
        solve();
    }

    return 0;
}

T4 旅游巴士

题目描述

理论分析

        这是一个分层图问题,即图上每个点要拆成k个点。拆点的依据是原本的点 mod\begin{matrix} & \end{matrix} k 意义下的到达时间。我们使用ans[i][j]表示到第i个点满足时间 mod\begin{matrix} & \end{matrix} k 的最短时间。初始条件为 ans[1][0]=0,然后跑最短路就行了。
        相对难处理的点在于每条边经过的时间限制,对于这一点,我们可以原地等待k的倍数时间达成(其实相当于出发时间晚了k的倍数时间)。

代码实现
#include <bits/stdc++.h>
#define int long long
using namespace std;

int ans[20005][100];
vector<vector<pair<int, int>>> g;

signed main(){
    int n, m, k;
    cin>>n>>m>>k;
    g.resize(n+1);
    int u, v, a;
    while(m--){
        cin>>u>>v>>a;
        g[u].push_back(pair<int, int> (v, a));
    }
    for(int i=1; i<=n; i++)
        for(int j=0; j<k; j++) ans[i][j]=0x7f7f7f7f7f7f7f7f;
    queue<pair<int, int>> q;
    q.push(pair<int, int> (1, 0));
    ans[1][0]=0;
    int now, pos, need;
    while(!q.empty()){
        auto t=q.front();
        q.pop();
        for(int i=0; i<g[t.first].size(); i++){
            now=g[t.first][i].first;
            pos=(t.second+1)%k;
            need=ans[t.first][t.second];
            if(need<g[t.first][i].second) need=need+(g[t.first][i].second-need+k-1)/k*k;
            need++;
            if(ans[now][pos]==-1||need<ans[now][pos]){
//                cout<<now<<" "<<pos<<" "<<need<<endl;
                ans[now][pos]=need;
                q.push(pair<int, int> (now, pos));
            }
        }
    }
    if(ans[n][0]>=0x7f7f7f7f7f7f7f7f) cout<<-1<<endl;
    else cout<<ans[n][0]<<endl;

    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值