2020牛客寒假算法基础集训营4 题解(部分)

链接:https://ac.nowcoder.com/acm/contest/3005
来源:2020牛客寒假算法基础集训营4

A 欧几里得(规律)

  思路:手推规律发现第三项之后可形成斐波那契数列。

#include<bits/stdc++.h>
using namespace std;
 
typedef long long ll;
const int maxn=82;
ll fib[maxn]={1,3,5};
 
int main(){
    for(int i=3;i<=maxn;i++) fib[i]=fib[i-1]+fib[i-2];
    int T; scanf("%d",&T);
    while(T--){
        int n; scanf("%d",&n);
        printf("%lld\n",fib[n]);
    }
    return 0;
}

B 括号序列(贪心)

  思路:经典贪心问题,当我们遇到左半部分的时候,将字符存入栈中,当遇到右半部分的时候,我们检查栈顶是否为是左半部分(此时需要注意栈顶为空的时候),如果匹配就弹出栈顶,如果不匹配可以放入栈顶。最后查看栈中是否还有字符即可(如果全部匹配,那么栈中所有字符都会弹出)。

#include<bits/stdc++.h>
using namespace std;
 
typedef long long ll;
const int maxn=1e6+10;
char str[maxn];
 
stack<char>s;
 
int main(){
    scanf("%s",str);
    int n=strlen(str);
    for(int i=0;i<n;i++){
        if(str[i]=='('||str[i]=='{'||str[i]=='['||!s.size()){
            s.push(str[i]); continue;
        }
        if(str[i]==')'){
            if(s.top()=='(') s.pop();
            else s.push(str[i]);
        }else if(str[i]==']'){
            if(s.top()=='[') s.pop();
            else s.push(str[i]);
        }else if(str[i]=='}'){
            if(s.top()=='{') s.pop();
            else s.push(str[i]);
        }
    }
    if(s.size()) puts("No");
    else puts("Yes");
    return 0;
}

C 子段乘积(分数取模)

  思路:从头开始遍历,我们首先乘出来 k k 个数,如果遇到 0 0 ,我们可以想到所有含有 0 0 的区间最大值都是 0 0 ,所以含有 0 0 的区间我们可以直接略过,也就是说如果某个位置上是 0 0 ,我们可以不计算它。此时我们考虑区间内满足条件的都不是 0 0 的时候,我们先计算出 k k 个数的乘积 a n s ans ,当我们在向右走的时候,我们想要得到满足条件的区间 此时需要做的是 ( a n s a [ i ] / a [ i k ] ) % m o d (ans*a[i]/a[i-k])\%mod 此时就需要使用乘法逆元。如果不对上面我所说的 0 0 的情况不进行处理,那么我们在模拟上述的过程中我出现 0 / 0 *0 和 /0 的情况。

#include<bits/stdc++.h>
using namespace std;
 
typedef long long ll;
const int mod=998244353;
const int maxn=2e5+10;
int a[maxn];
 
ll quickPow(ll a,ll b){
    ll ans=1,res=a;
    while(b){
        if(b&1) ans=ans*res%mod;
        res=res*res%mod;
        b>>=1;
    }
    return ans%mod;
}
 
int main(){
    int n,k; scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    if(k>=n){
        ll sum=1;
        for(int i=1;i<=n;i++) sum=sum*a[i]%mod;
        printf("%lld\n",sum);
    }else{
        ll sum=1,cnt=0,mmax=0;
        for(int i=1;i<=n;i++){
            cnt++;
            if(a[i]==0) { cnt=0;sum=1;continue; }
            sum=sum*a[i]%mod;
            if(cnt==k) mmax=max(mmax,sum);
            if(cnt==k+1){
                cnt--;
                sum=sum*quickPow(a[i-k],mod-2)%mod;
                mmax=max(mmax,sum);
            }
        }
        printf("%lld\n",mmax);
    }
    return 0;
}

D 子段异或(异或性质)

  思路:我们知道 A A ^ A A = 0 0 0 0 ^ A = A A=A 我们先维护一个前缀异或和,如图:
在这里插入图片描述
  我们假设如果有这么一个前缀和,我们可以看到 s u m [ 1 ] = s u m [ 2 ] = s u m [ 3 ] = s u m [ 4 ] = 0 sum[1] = sum[2] = sum[3] = sum[4] = 0 s u m [ 5 ] = s u m [ 6 ] = s u m [ 7 ] = s u m [ 8 ] = s u m [ 9 ] sum[5] = sum[6] = sum[7] = sum[8] = sum[9] ,如果有 i < j i < j ,就有:
   s u m [ i ] = a [ 1 sum[i] = a[1 ] ^ a [ 2 a[2 ] ^ . . . . .... ^ a [ i ] a[i]       s u m [ i ] = s u m [ j ] sum[i] = sum[j]
   s u m [ j ] = a [ 1 ] sum[j] = a[1] ^ a [ 2 ] a[2] *^ . . . . .... ^ a [ i ] a[i] ^ a [ i + 1 ] a[i+1] ^ . . . . .... ^ a [ j ] = s u m [ j ] a[j] = sum[j] ^ a [ i + 1 ] a[i+1] ^ . . . . .... ^ a [ j ] a[j]
则有 a [ i + 1 ] a[i+1] ^ . . . . .... ^ a [ j ] = 0 a[j] = 0
  所以我们只需要知道 s u m sum 出现的次数即可,如果是 0 0 的话,那么就要算上本身的 0 0 ,我们可以找到 c n t ( c n t + 1 ) / 2 cnt*(cnt+1)/2 个区间,如果不是 0 0 ,我们可以找到 c n t ( c n t 1 ) / 2 cnt*(cnt-1)/2 。如果一个数已经计算过,那么后面就不用再次计算满足题意的区间。

//我的程序复杂了,直接开cnt[]数组,计算每个数字出现了多少次即可。
#include<bits/stdc++.h>
using namespace std;
 
typedef long long ll;
const int maxn=2e5+10;
ll a[maxn],sum[maxn],sum1[maxn];
map<ll,int>m;
 
int main(){
    //cout<<(2^3^4^5)<<endl;
    int n; scanf("%d",&n);
    for(int i=0;i<n;i++){
        scanf("%lld",&a[i]);
        if(i) sum[i]=sum[i-1]^a[i];
        else sum[i]=a[i];
        sum1[i]=sum[i];
        m[sum[i]]=1;
    }
    //for(int i=0;i<n;i++) cout<<sum[i]<<" "; cout<<endl;
    sort(sum1,sum1+n);
    ll ans=0;
    for(int i=0;i<n;i++){
        int l=lower_bound(sum1,sum1+n,sum[i])-sum1;
        int r=upper_bound(sum1,sum1+n,sum[i])-sum1-1;
        ll cnt=r-l+1;
        if(m[sum[i]]){
            if(sum[i]==0){
                ans+=cnt*(cnt+1)/2;
            }else{
                ans+=cnt*(cnt-1)/2;
            }
            m[sum[i]]=0;
        }
    }
    printf("%lld\n",ans);
    return 0;
}

  上面的思路其实被我想复杂了,我们可以一边记录 s u m sum 出现的次数,一边计算满足条件的区间,在计算前缀和的过程中,如果前面出现过多少次 s u m sum ,那么对于这个点来说就会有几个满足条件的区间,如果想要包含 l = = r l==r 的情况,我们只需要初始化 m [ 0 ] = 1 m[0]=1 即可。

#include<bits/stdc++.h>
using namespace std;
 
typedef long long ll;
const int maxn=2e5+10;
ll a[maxn],sum[maxn];
map<ll,int>m;
 
int main(){
    int n; scanf("%d",&n);
    ll ans=0; m[0]=1;
    for(int i=1;i<=n;i++){
        scanf("%lld",&a[i]);
        sum[i]=sum[i-1]^a[i];
        //cout<<sum[i]<<" "<<m[sum[i]]<<endl;
        ans+=(ll)m[sum[i]];
        m[sum[i]]++;
    }
    printf("%lld\n",ans);
    return 0;
}

E 最小表达式(思维+大数)

  思路:首先我们先把所有的数字字符放入另外一个数组中,将 + + 号字符放入栈 s s 中,很容易看出来我们需要对所有的数字字符进行排列,然后我们可以把这些数字字符分成 s . s i z e ( ) + 1 s.size()+1 组,比赛的时候我是把这些数字字符分组放入一个二维数组中,之后进行大数加法,这种方法超时,但是我又想不出其他做法。赛后看了题解才知道我们可以按位直接做加法,也就是我们从后往前遍历,每次经过 s . s i z e ( ) + 1 s.size()+1 个数字,就代表着进位一次。(因为我们要把这些数字分成 s . s i z e ( ) + 1 s.size()+1 组,那么从后往前,前面 s . s i z e ( ) + 1 s.size()+1 个数字一定都是每组数字的个位,我们把它们相加即可,下一个 s . s i z e ( ) + 1 s.size()+1 个数字就是十位,以此类推直到全部算完为止。这样一边计算每一位,一边进位就可以得到最终结果。

#include<bits/stdc++.h>
using namespace std;
 
typedef long long ll;
const int maxn=5e5+10;
char str[maxn],str1[maxn];
int ans[maxn];
 
stack<int>s;
 
int main(){
    scanf("%s",str);
    int n=strlen(str);
    int cnt=0;
    for(int i=0;i<n;i++){
        if(str[i]=='+') s.push(str[i]);
        else str1[cnt++]=str[i];
    }
    sort(str1,str1+cnt);
    //puts(str1);
    int len=0,cntNumber=0;
    for(int i=cnt-1;i>=0;i--){
        cntNumber++;
        if(cntNumber==(int)s.size()+1||i==0){
            ans[len++]+=str1[i]-'0';
            ans[len]=ans[len-1]/10;
            ans[len-1]%=10;
            cntNumber=0;
        }else ans[len]+=str1[i]-'0';
    }
    if(ans[len]) printf("%d",ans[len]);
    for(int i=len-1;i>=0;i--) printf("%d",ans[i]);
    return 0;
}

F 树上博弈(思维+搜索)

  思路:必胜策略:两人相距的距离为偶数。直接使用 d f s dfs 或者 b f s bfs 计算出每个点距离根结点的距离。算出距离后如下图:
在这里插入图片描述
  看图我们可以知道所有黑点之间可以形成基数的距离,所有白点之间也都可以形成偶数距离,直接利用求和公式即可算出答案。

//dfs
#include<bits/stdc++.h>
using namespace std;
 
typedef long long ll;
const int maxn=1e6+10;
int cnt=0,head[maxn],deep[maxn]={-1};
 
struct Edge{
    int u,v,next;
}edge[maxn<<1];
 
void addedge(int u,int v){
    edge[cnt].u=u;
    edge[cnt].v=v;
    edge[cnt].next=head[u];
    head[u]=cnt++;
}
 
void dfs(int fa,int v){
    deep[v]=deep[fa]+1;
    for(int i=head[v];i!=-1;i=edge[i].next){
        if(edge[i].v==fa) continue;
        dfs(v,edge[i].v);
    }
}
 
int main(){
    int n; scanf("%d",&n);
    for(int i=1;i<=n;i++) head[i]=-1;
    for(int i=2;i<=n;i++){
        int fa; scanf("%d",&fa);
        addedge(fa,i); addedge(i,fa);
    }
    dfs(0,1);
    int cnt0=0,cnt1=0;
    for(int i=1;i<=n;i++){
        if(deep[i]&1) cnt1++;
        else cnt0++;
    }
    ll ans=1ll*(cnt0-1)*cnt0;
    ans+=1ll*cnt1*(cnt1-1);
    printf("%lld\n",ans);
    return 0;
}
#include<bits/stdc++.h>
using namespace std;
 
typedef long long ll;
const int maxn=1e6+10;
int cnt=0,head[maxn],deep[maxn];
bool vis[maxn];
 
struct Edge{
    int u,v,next;
}edge[maxn<<1];
 
struct Node{
    int n,deep;
    Node(){};
    Node(int n,int deep):n(n),deep(deep){};
};
 
void addedge(int u,int v){
    edge[cnt].u=u;
    edge[cnt].v=v;
    edge[cnt].next=head[u];
    head[u]=cnt++;
}
 
void bfs(int s){
    queue<Node>q; q.push(Node(s,0));
    while(!q.empty()){
        Node node=q.front(); q.pop();
        deep[node.n]=node.deep;
        for(int i=head[node.n];i!=-1;i=edge[i].next){
            if(!vis[edge[i].v]){
                vis[edge[i].v]=true;
                q.push(Node(edge[i].v,node.deep+1));
            }
        }
    }
}
 
int main(){
    int n; scanf("%d",&n);
    for(int i=1;i<=n;i++) head[i]=-1;
    for(int i=2;i<=n;i++){
        int fa; scanf("%d",&fa);
        addedge(fa,i); addedge(i,fa);
    }
    bfs(1);
    int cnt0=0,cnt1=0;
    for(int i=1;i<=n;i++){
        if(deep[i]&1) cnt1++;
        else cnt0++;
    }
    ll ans=1ll*cnt0*(cnt0-1);
    ans+=1ll*cnt1*(cnt1-1);
    printf("%lld\n",ans);
    return 0;
}

I 匹配星星(贪心+STL)

  让我们先来看一个三维的坐标图

在这里插入图片描述
  我们对这些点,先按照 x x 进行排序,如果 x x 相同,我们就按照 z z 排序,然后我们遍历数组,如果我们遍历到一个点的 z z 0 0 ,那么我们将它的 y y 值存入 m u l t i s e t multiset ,如果这个点的 z z 1 1 ,那么我们之前存入 y y 的点的 x z x,z 值一定都比当前的值小,我们只需要关注 y y 即可,只要能够找到比当前点的 y y 值小的点,就可以完成配对,将点从 m u l t i s e t multiset 中删除。我们利用 m u l t i s e t multiset 二分查找到 y ≥ y 的迭代器的位置,那么此位置前面一个位置存入的值一定比 y y 小(如果当前迭代器位置不是 s . b e g i n ( ) s.begin() 的话),找到以后就删除前面一个迭代器位置上的值,配对值加一。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
 
struct Node{
    int x,y,z;
    bool operator<(const Node &node) const{
        if(x==node.x) return z<node.z;
        return x<node.x;
    }
}node[maxn];
 
multiset<int>ms;
 
int main(){
    int n; scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d%d%d",&node[i].x,&node[i].y,&node[i].z);
    sort(node+1,node+n+1);
    int ans=0;
    for(int i=1;i<=n;i++){
        if(node[i].z==0) ms.insert(node[i].y);
        else{
            //>=当前点y的第一个迭代器的位置,前面一个位置就是要找到的需要匹配的点
            multiset<int>::iterator it=ms.lower_bound(node[i].y);
            if(it==ms.begin()) continue;
            ms.erase(--it);
            ans++;
        }
    }
    printf("%d\n",ans);
    return 0;
}
发布了166 篇原创文章 · 获赞 68 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_42217376/article/details/104281960
今日推荐