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 子段乘積(分數取模)

  思路:從頭開始遍歷,我們首先乘出來 kk 個數,如果遇到 00,我們可以想到所有含有 00 的區間最大值都是 00,所以含有 00 的區間我們可以直接略過,也就是說如果某個位置上是00,我們可以不計算它。此時我們考慮區間內滿足條件的都不是 00 的時候,我們先計算出 kk 個數的乘積 ansans,當我們在向右走的時候,我們想要得到滿足條件的區間 此時需要做的是 (ansa[i]/a[ik])%mod(ans*a[i]/a[i-k])\%mod 此時就需要使用乘法逆元。如果不對上面我所說的 00 的情況不進行處理,那麼我們在模擬上述的過程中我出現 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 子段異或(異或性質)

  思路:我們知道 AA ^ AA = 0000 ^ A=AA=A 我們先維護一個前綴異或和,如圖:
在這裏插入圖片描述
  我們假設如果有這麼一個前綴和,我們可以看到 sum[1]=sum[2]=sum[3]=sum[4]=0sum[1] = sum[2] = sum[3] = sum[4] = 0sum[5]=sum[6]=sum[7]=sum[8]=sum[9]sum[5] = sum[6] = sum[7] = sum[8] = sum[9],如果有 i<ji < j,就有:
  sum[i]=a[1sum[i] = a[1] ^ a[2a[2] ^ ........ ^ a[i]a[i]      sum[i]=sum[j]sum[i] = sum[j]
  sum[j]=a[1]sum[j] = a[1] ^ a[2]a[2] *^ ........ ^ a[i]a[i] ^ a[i+1]a[i+1] ^ ........ ^ a[j]=sum[j]a[j] = sum[j] ^ a[i+1]a[i+1] ^ ........ ^ a[j]a[j]
則有 a[i+1]a[i+1] ^ ........ ^ a[j]=0a[j] = 0
  所以我們只需要知道 sumsum出現的次數即可,如果是 00 的話,那麼就要算上本身的 00,我們可以找到 cnt(cnt+1)/2cnt*(cnt+1)/2個區間,如果不是 00,我們可以找到 cnt(cnt1)/2cnt*(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;
}

  上面的思路其實被我想複雜了,我們可以一邊記錄 sumsum 出現的次數,一邊計算滿足條件的區間,在計算前綴和的過程中,如果前面出現過多少次 sumsum,那麼對於這個點來說就會有幾個滿足條件的區間,如果想要包含 l==rl==r 的情況,我們只需要初始化 m[0]=1m[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 最小表達式(思維+大數)

  思路:首先我們先把所有的數字字符放入另外一個數組中,將 ++ 號字符放入棧 ss 中,很容易看出來我們需要對所有的數字字符進行排列,然後我們可以把這些數字字符分成 s.size()+1s.size()+1 組,比賽的時候我是把這些數字字符分組放入一個二維數組中,之後進行大數加法,這種方法超時,但是我又想不出其他做法。賽後看了題解才知道我們可以按位直接做加法,也就是我們從後往前遍歷,每次經過 s.size()+1s.size()+1 個數字,就代表着進位一次。(因爲我們要把這些數字分成 s.size()+1s.size()+1 組,那麼從後往前,前面s.size()+1s.size()+1個數字一定都是每組數字的個位,我們把它們相加即可,下一個s.size()+1s.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 樹上博弈(思維+搜索)

  思路:必勝策略:兩人相距的距離爲偶數。直接使用 dfsdfs 或者 bfsbfs 計算出每個點距離根結點的距離。算出距離後如下圖:
在這裏插入圖片描述
  看圖我們可以知道所有黑點之間可以形成基數的距離,所有白點之間也都可以形成偶數距離,直接利用求和公式即可算出答案。

//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)

  讓我們先來看一個三維的座標圖

在這裏插入圖片描述
  我們對這些點,先按照 xx 進行排序,如果 xx 相同,我們就按照 zz排序,然後我們遍歷數組,如果我們遍歷到一個點的 zz00,那麼我們將它的 yy 值存入 multisetmultiset ,如果這個點的 zz11,那麼我們之前存入 yy 的點的 xzx,z 值一定都比當前的值小,我們只需要關注 yy 即可,只要能夠找到比當前點的 yy 值小的點,就可以完成配對,將點從 multisetmultiset 中刪除。我們利用 multisetmultiset 二分查找到 y≥ y 的迭代器的位置,那麼此位置前面一個位置存入的值一定比 yy 小(如果當前迭代器位置不是 s.begin()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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章