牛客周赛67文字版题解

写在前面

又写篇题解吧~
这场的题整体来说非常友好,也非常适合相关算法的练习,很适合补完。
如果哪题遇到解决不了的问题,或者有什么好的建议给到牛牛,欢迎留言评论区,我们会尽快给出答复。

alt

A.排序危机

知识点: 基础语法 只需要按顺序遍历给定的字符串,然后分别把小写字母,大写字母,数字存储在三个不同的字符串,按照顺序输出即可。

void solve(){
    int n;
    cin>>n;
    string s;
    cin>>s;
    string a,A,num;
    for(auto t:s){
        if(t>='a'&&t<='z') a+=t;
        else if(t>='A'&&t<='Z') A+=t;
        else num+=t;
    }
    cout<<a<<num<<A;
}

B.小歪商店故事:卷

知识点: 数学思维

直接比较每个物品的价格 的大小。

直接除可能产生精度误差,所以我们采用交叉相乘,然后找到 使得 ,即 ,然后初始时若两个价格相等,则算完还是相同,故需要

void solve(){
    int _;
    cin>>_;
    for(int __=1;__<=_;__++){
        i64 a,b,c,d;
        cin>>a>>b>>c>>d;
        i64 newa=c*b/d;
        if(newa*d>=b*c) newa--;
        cout<<a-newa<<" ";
    }
}

C.小苯的计算式

知识点: 数学思维

由于两个非负数 相加要等与 ,必然满足 ,枚举其实中 ,然后通过减法求 ,看总的数位之和是否相等 ,满足时答案

其中数位计算可以采用 ,特判 ,防止产生数学上的错误。

void solve(){
    int n,C;
    cin>>n>>C;
    int n1=2+(int)log10(C)+1;
    i64 ans=0;
    for(int a=0;a<=C;a++){
        int wa;
        if(a==0) wa=1;
        else wa=(int)log10(a)+1;
        int b=C-a;
        int wb;
        if(b==0) wb=1;
        else wb=(int)log10(b)+1;
        if(wa+wb+n1==n) ans++;
    }
    cout<<ans<<"\n";
    
}

D.K

知识点: 构造 数学思维

我们可以考虑 相间的构造法。

这样满足极大不同区间长度为 ,最多可构造 个区间。

当所有数都为相同时,可构造 个区间。

所以可得 时都有解,否则无解。

故我们先构造 个极大不同区间,可以先考虑 , 那么即满足 ,这样便是 个,故需要 个数。为防止增长极大不同区间长度,后 个数与 这个位置相同的即可。

void solve(){
    int n,k;
    cin>>n>>k;
    if(k>n){
        cout<<"NO";
        return ;
    }
    cout<<"YES\n";
    if(k==n){
        for(int i=1;i<=n;i++) cout<<1<<" ";
        return ;
    }
    for(int i=1;i<=k+1;i++){
        cout<<(i&1)<<" ";
    }
    for(int i=k+2;i<=n;i++){
        cout<<((k+1)&1)<<" ";
    }
}

E.小苯的区间选数

知识点: 数位dp 数学思维(如果采用直接枚举,而不是题解中的做法)

验题的时候写了全错的然后 了,两天后的一个早晨突然悟到,也就有了现在的数据。

alt

又是数位 ,但是也可以不用, 选手写题已经习惯火箭式起飞。

跟周赛 的做法完全一样,可以发现代码细节都没啥变,就是多了个上下界。

题目等价于求 的最大数位和。

:表示枚举到第 位,前面数字总和为 可得的数位最大和。

由于两个数字长度可能不一,所以将 加上前导 即可。

int dp[20][200];
int cul(string l,string r,int w,bool u,bool d,bool ld0,int sum){
    if(w==l.size()) return sum;
    if(!u&&!d&&!ld0&&dp[w][sum]!=-1) return dp[w][sum];
    int up=u?r[w]-'0':9;
    int down=d?l[w]-'0':0;
    int ans=0;
    for(int i=down;i<=up;i++){
        int temp=cul(l,r,w+1,u&&(i==r[w]-'0'),d&&(i==l[w]-'0'),ld0&&(i==0),(ld0&&i==0)?0:(sum+i));
        ans=max(ans,temp);
    }
    if(!u&&!d&&!ld0){
        dp[w][sum]=ans;
    }
    return ans;
}
int get(i64 l,i64 r){
    string sl=to_string(l);
    string sr=to_string(r);
    string t="";
    while(t.size()+sl.size()<sr.size()) t+='0';
    t+=sl;
    sl=t;
    memset(dp,-1,sizeof dp);  
    return cul(sl,sr,0,1,1,1,0);
}
void solve(){
    int t;
    cin>>t;
    while(t--){
        i64 l1,r1,l2,r2;
        cin>>l1>>r1>>l2>>r2;
        cout<<get(l1+l2,r1+r2)<<"\n";
    }
}

F.小Z的树迁移

知识点: 前缀和 dfs序 RMQ

刚写的时候多枚举了跳的步数的 ,导致被卡的 叫。

如果所示,当我们考虑这样的一棵树

alt

当我们查询 时应该如何做?

第一步:不难想到 向下走 步必然位于 的结点上。 当 或者没有结点存在于 必然是无解。

第二步,对于 如果存在点,那么必然也是其子树下的点才是可以到达的点,例如: ,那么 ,其结点集合为 ,根据图可以观察到其实只有 才是满足条件的点,那这些点是如何算出来的,我们考虑树的遍历顺序,即 序,如图所示(红色字迹)为每个结点的 序。

不难观察到对于结点 可到达的点的 序为 。那么我们就可以把每个点存在对应的深度容器里,然后根据 序从小到大排序,这样对于一个点可到达的点便是一一段连续的区间,如果不存在其 序范围内的也是无解。

第三步: 如果维护这些结点的最大值呢? 我们预处理每个点到达根节点路径的最大值,然后这样在每个对应深度的容器里维护这些的最大值。

当然,当询问的点不是根节点时,必然存在多余的路径,例如 ,我们查询到了 这三个结点到达根节点路径的最大值,但是显然多了结点 ~ 结点 这段路径的权值,所以还要扣掉这段的权值。

由于空间不定,所以数组不能开,考虑动态容器

时间复杂度

#include<bits/stdc++.h>
#ifndef ONLINE_JUDGE
#include<bits/Bingbong.h>
#endif
using namespace std;
#define i64 long long
#define pII pair<int,int>
const int N=1e5+1;
int dep[N],dfn[N],ls[N],rs[N];
i64 val[N];
void solve(){
    int n;
    cin>>n;
    vector<vector<pII>>e(n+1);
    for(int i=1;i<n;i++){
        int u,v,w;
        cin>>u>>v>>w;
        e[u].push_back({v,w});
        e[v].push_back({u,w});
    }
    vector<vector<int>>dpo(n+1);// 存位于第i层的结点
    int cnt=0;
    dep[1]=1;
    auto dfs1=[&](auto &&self,int u,int fa)->void {
        dfn[u]=++cnt;
        ls[u]=cnt;
        for(auto v:e[u]){
            if(v.first==fa) continue;
            dep[v.first]=dep[u]+1;
            dpo[dep[v.first]].push_back(v.first);
            val[v.first]=val[u]+v.second;
            self(self,v.first,u);
        }
        rs[u]=cnt;
        return ;
    };
    auto camp=[&](int i,int j)->bool {
        return dfn[i]<dfn[j];
    };
    dfs1(dfs1,1,-1);
    for(int i=1;i<=n;i++){
        sort(dpo[i].begin(),dpo[i].end(),camp);
    }
    vector<vector<vector<i64>>>dp(n+1);
    // 深度为i,从x开始,长度为 (1<<y) 的最大值
    for(int i=1;i<=n;i++){
        if(dpo[i].size()==0) continue;
        dp[i].resize(dpo[i].size()+1);
        int sz=log2((int)dpo[i].size())+1;
        for(int x=0;x<dpo[i].size();x++){
            dp[i][x].resize(sz);
            dp[i][x][0]=val[dpo[i][x]];
        }
        for(int y=1;y<sz;y++){
            for(int x=0;x<dpo[i].size()&&(x+(1<<y)-1)<dpo[i].size();x++){
                dp[i][x][y]=max(dp[i][x][y-1],dp[i][x+(1<<(y-1))][y-1]);
            }
        }
        
    }
    auto ask=[&](int depth,int l,int r)->i64 {
        i64 ans=0;
        int len=log2(r-l+1);
        ans=max(ans,dp[depth][l][len]);
        ans=max(ans,dp[depth][r-(1<<len)+1][len]);
        return ans;
    };
    int q;
    cin>>q;
    while(q--){
        int u,d;
        cin>>u>>d;
        int nd=dep[u]+d;
        if(nd>n||dpo[nd].size()==0){
            cout<<"-1\n";
            continue;
        }
        // 先找出 dfn在 l[u] 和 r[u] 之间的点。
        int l1=0,r1=dpo[nd].size()-1;
        int ansl=-1,ansr=-1;
        while(l1<r1){
            int mid=(l1+r1)>>1;
            if(dfn[dpo[nd][mid]]>=ls[u]) {
                r1=mid;
                ansl=mid;
            }
            else l1=mid+1;
        }
        if(dfn[dpo[nd][l1]]>=ls[u]){
            ansl=l1;
        }
        if(ansl==-1){
            cout<<"-1\n";
            continue;
        }
        int l2=0,r2=dpo[nd].size()-1;
        while(l2<r2){
            int mid=(l2+r2+1)>>1;
            if(dfn[dpo[nd][mid]]<=rs[u]) {
                l2=mid;
                ansr=mid;
            }else r2=mid-1;
        }
        if(dfn[dpo[nd][l2]]<=rs[u]){
            ansr=l2;
        }
        if(ansr==-1||ansl>ansr){
           cout<<"-1\n";
            continue;
        }
        i64 temp=val[u]-val[1];
        cout<<ask(nd,ansl,ansr)-temp<<"\n";
    }    
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t=1;
    while(t--) {
        solve();
    }
    return 0;
}
全部评论
出题人你好,这是我在E题中发现的一个小问题: 当输入的测试样例为 1 1 1 998 2000 时,正确答案应该是由数字1999得出的28,但是我的代码得出的是由数字999得出的27,而我的代码AC了,我的AC代码如下,AC了之后我才发现当start和end长度不一样时我的处理有问题 t = int(input()) for i in range(t):     l1,r1,l2,r2 = map(int,input().split())     res = 0     start = l1 + l2     end = r1 + r2     str_start = str(start)     str_end = str(end)     len_start = len(str_start)     len_end = len(str_end)     res1 = sum([int(x) for x in str(end)])     if start == end:         print(res1)         continue     elif len_start != len_end:  # 这里有问题         cnt = 10**(len_end - 1) - 1         res2 = sum([int(x) for x in str(cnt)])      else:         for j in range(len_start):             if str_start[j] != str_end[j]:                 cnt = end - end % 10**(len_end-1-j) - 1                 res2 = sum([int(x) for x in str(cnt)])                  break     print(max(res1,res2))
5 回复 分享
发布于 11-10 22:25 浙江
ds选手是什么意思
1 回复 分享
发布于 11-11 15:20 江西
d题标答都是错最后一个点
点赞 回复 分享
发布于 11-10 22:56 河南
E题配图好到位啊
点赞 回复 分享
发布于 11-11 10:17 广东
f的树可以有环吗
点赞 回复 分享
发布于 11-15 10:15 山东

相关推荐

我见java多妩媚:大外包
点赞 评论 收藏
分享
评论
6
2
分享
牛客网
牛客企业服务