牛客小白月赛13 个人学习笔记 题解

题目链接:https://ac.nowcoder.com/acm/contest/549#question

 

 先贴一波官方题解

A-小A的签到题

显然复制代码并不能AC,实际上要求的是f2n−fn−1fn+1fn2−fn−1fn+1,其中fnfn是斐波那契数列的第n项,简单观察或推导可以得出结论:∀n≥3∀n≥3, 若为奇数则为-1,否则就为1。

复杂度:O(1)O(1)

B-小A的回文串
求n个串的最大回文子串的长度的最大值。枚举每一个变化后的字符串,对每个串跑一遍马拉车即可。

复杂度:O(n2)O(n2)

C-小A买彩票
考虑买n张彩票的总的方案数是4n4n,然后统计不亏本的方案数,记录f[i][j]f[i][j]是买到第i张彩票总获利为j的总方案数。f[i][j]=∑f[i−1][j−k],k=1,2,3,4f[i][j]=∑f[i−1][j−k],k=1,2,3,4 ,最后统计一下不亏本的方案数即可。由于数据规模很小,考虑分别组合枚举有多少个1,2,3,4也可以通过。

复杂度:O(n2)O(n2)

D-小A的位运算
预处理了一下前缀和后缀,然后枚举那个不选的数就可以了。

复杂度:O(n)O(n)

E-小A的路径
矩阵表示第一天的时候u到v有多少条路径,然后直接做矩阵k次幂就能得到k天从u到v的路径数,最后统计一下就可以了。

复杂度:O(n3logk)O(n3logk)

F-小A的最短路
这张图可以认为是边权全为1的树上增加了一条边权为0的边。

首先先不考虑多出来的一条边,那么dep[u]表示点u的深度,任意两点u,v的最短的距离就是dep[u]+dep[v]−2dep[lca(u,v)]dep[u]+dep[v]−2dep[lca(u,v)],加上这条边后另外一种可能最短的路径就是经过这条边权为0的边,可以建图之后跑一次最短路,每次查询的答案都是min(dis[u]+dis[v],dep[u]+dep[v]−2dep[lca(u,v)])min(dis[u]+dis[v],dep[u]+dep[v]−2dep[lca(u,v)])。

复杂度:O((n+q)logn)O((n+q)logn)

G-小A和小B
这题由于疏忽,一开始的时候题意上误导了大家,在这里向大家道歉。
双向BFS,只要搜索的时候判断当前走过的位置是否被对方走过就可以了。

复杂度:O(nm)O(nm)

H-小A的柱状图
单调栈的模板题,考虑每个位置向两边拓展的最左边的边界和最右边的边界L[i],R[i]L[i],R[i],分别从左到右和从右到左用一个单调栈维护。处理一下底边的前缀和,扫一遍就可以求出最大值。

复杂度:O(n)O(n)

I-小A取石子
判断一下小A是否能够取石子,当且仅当小A原本就是必败态并且不能取出任何石子的情形下,小A会输否则小A都会赢。

复杂度:O(n)O(n)

J-小A的数学题
有很多种做法,一种比较常规的做法是:

∑ni=1∑mj=1gcd(i,j)2∑i=1n∑j=1mgcd(i,j)2
=∑min(n,m)d=1d2(∑ni=1∑mj=1gcd(i,j)=d)∑d=1min(n,m)d2(∑i=1n∑j=1mgcd(i,j)=d)
记f(d)=(∑ni=1∑mj=1gcd(i,j)=d)记f(d)=(∑i=1n∑j=1mgcd(i,j)=d)
有F(t)=∑d|tf(d)=[nd][md]有F(t)=∑d|tf(d)=[nd][md]
有f(d)=∑d|tμ(td)F(t)=∑d|tμ(td)[nd][md]有f(d)=∑d|tμ(td)F(t)=∑d|tμ(td)[nd][md]
处理因子直接计算复杂度O(nlogn)O(nlogn)就可以通过本题

A标程略
B:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=40519506
C:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=40519522
D:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=40519534
E:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=40519540
F:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=40519544
G:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=40519557
H:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=40519566
I:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=40519576
J:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=40519587

 

 

A.小A的签到题

链接:https://ac.nowcoder.com/acm/contest/549/A
来源:牛客网
 

题目描述

这是一道签到题,主要考验比赛时的手速。

接下来是一段很简单的代码,或许你提交它就可以AC。

#include<bits/stdc++.h>
using namespace std;
int main() {
    long long n;
    scanf("%lld",&n);
    long long f1=1,f2=1,f3;
    for(int i=3;i<=n;i++) {
        f3=f1+f2;
        f1=f2;
        f2=f3;
    }
    printf("%lld\n",f3*f3-f1*f1-f1*f3);
    return 0;
}

 

输入描述:

一行一个整数N一行一个整数N

输出描述:

一行一个整数表示结果一行一个整数表示结果

示例1

输入

复制

3

输出

复制

1

备注:

3≤N≤1e11

 

说是签到题,其实是找规律题,10^11的数据,O(n)也会超时,同时我们也要考虑下会不会数据溢出,所以再枚举1-10的时候,我们可以找到规律 奇数为1,偶数为-1,考心态的规律题?

#include<bits/stdc++.h>
using namespace std;
int main() {
    long long n;
    scanf("%lld",&n);
    if(n%2==1)
        printf("1\n");
    else
        printf("-1\n");
    return 0;
}

 

 

B.小A的回文串

链接:https://ac.nowcoder.com/acm/contest/549/B
来源:牛客网
 

题目描述

小A非常喜欢回文串,当然我们都知道回文串这种情况是非常特殊的。所以小A只想知道给定的一个字符串的最大回文子串是多少,但是小A对这个结果并不是非常满意。现在小A可以对这个字符串做一些改动,他可以把这个字符串最前面的某一段连续的字符(不改变顺序)移动到原先字符串的末尾。那么请问小A通过这样的操作之后(也可以选择不移动)能够得到最大回文子串的长度是多少。

输入描述:

一行一个字符串表示给定的字符串S一行一个字符串表示给定的字符串S

输出描述:

一行输出一个整数,表示通过这样的操作后可以得到最大回文子串的长度。

示例1

输入

复制

dcbaabc

输出

复制

7

说明

将前面的dcba移动到末尾变成abcdcba,这个字符串的最大回文子串就是它本身,长度为7

备注:

N表示字符串的长度,1≤N≤5000

一个有点改动的求最长回文子串的题,有些大佬将它化为环形来做,这样就降低了一层O(n)枚举

数据貌似有点弱,先来一发暴力,前2个for枚举起点和终点,最后1个for匹配

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

int main(){
    ios::sync_with_stdio(false);
    string s;
    cin>>s;
    s = s.append(s);
    //cout<<s<<endl;
    int len = s.size()/2;
    int ans=0;
    for(int i=0;i<len;i++)
        for(int j=len-1+i;j>=i;j--){//前2个for枚举起点和终点,最后1个for匹配
            int flag = 1;
            for(int k=i,p=j;k<=p;k++,p--)
                if(s[k]!=s[p]){
                    flag = 0;
                    break;
                }
            if(flag)
                ans = max(ans,j-i+1);
        }

    cout<<ans<<endl;

	return 0;
}

 

 

C.小A买彩票

链接:https://ac.nowcoder.com/acm/contest/549/C
来源:牛客网
 

题目描述

小A最近开始沉迷买彩票,并且希望能够通过买彩票发家致富。已知购买一张彩票需要3元,而彩票中奖的金额分别为1,2,3,4元,并且比较独特的是这个彩票中奖的各种金额都是等可能的。现在小A连续购买了n张彩票,他希望你能够告诉他至少能够不亏本的概率是多少。

输入描述:

一行一个整数N,为小A购买的彩票数量一行一个整数N,为小A购买的彩票数量

输出描述:

 

输出一个最简分数a/b,表示小A不亏本的概率。若概率为1,则输出1/1,概率为0,则输出0/1。输出一个最简分数a/b,表示小A不亏本的概率。若概率为1,则输出1/1,概率为0,则输出0/1。

示例1

输入

复制

2

输出

复制

3/8

备注:

0≤n≤30

这题和某人傻乎乎的再找规律,从n=1枚举到5,然鹅答案是dp


考虑买n张彩票的总的方案数是4n4n,然后统计不亏本的方案数,记录f[i][j]f[i][j]是买到第i张彩票总获利为j的总方案数。f[i][j]=∑f[i−1][j−k],k=1,2,3,4f[i][j]=∑f[i−1][j−k],k=1,2,3,4 ,最后统计一下不亏本的方案数即可。由于数据规模很小,考虑分别组合枚举有多少个1,2,3,4也可以通过。 

典型的递归公式qaq,相当于取第i张有多少种可能价值,然后累加...

有毒 W了好久,发现 dp[][]要longlong 

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

typedef long long ll;
const int N=35;

ll dp[N][4*N];

ll mpow(ll a,ll n){
    ll ans = 1;
    while(n>0){
        if(n%2==1)
            ans*=a;
        a*=a;
        n>>=1;
    }
    return ans;
}

ll gcd(ll a,ll b){
    return b==0?a:gcd(b,a%b);
}


int main(){
    int n;
    ll ans=0;
    scanf("%d",&n);
    dp[0][0] = 1;

    for(int i=1;i<=n;i++)
        for(int j=i;j<=4*i;j++)
            for(int k=1;k<=4;k++)
                if(j-k>=0) dp[i][j]+=dp[i-1][j-k];

    for(int i=3*n;i<=4*n;i++)
        ans+=dp[n][i];
    ll sum = mpow(4,n);
    ll g = gcd(sum,ans);

    printf("%lld/%lld\n",ans/g,sum/g);

	return 0;
}

 

D.小A的位运算

链接:https://ac.nowcoder.com/acm/contest/549/D
来源:牛客网
 

题目描述

位运算是一个非常重要的东西。而小A最近在学习位运算,小A看到了一道很简单的例题,是说从N个数里面选出N-1个数要让它们或起来的值最大,小A想知道这个答案是多少。你可以帮帮他吗?

输入描述:

第一行一个整数N表示有N个数接下来一行N个数表示A1,A2...AN第一行一个整数N表示有N个数接下来一行N个数表示A1,A2...AN

输出描述:

一行输出个结果代表最大值一行输出个结果代表最大值

示例1

输入

复制

5
1 2 4 8 16

输出

复制

30

说明

选择2,4,8,16或的和是最大的,没有比这个更大的方案。

备注:

 

1≤N≤5e6,1≤Ai≤longlong

 

这题我是排序后贪心,复杂度O(nlogn)

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;

#define ll long long

int main(){
    ll a[5000005];
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++)
        scanf("%lld",&a[i]);
    sort(a,a+n);
    ll sum = a[n-1];
    int cnt=1;
    for(int i=n-2;i>=0;i--)
        if((sum|a[i])>sum){
            sum = sum|a[i];
            cnt++;
            if(cnt>=n-1)
                break;
        }

    printf("%lld\n",sum);

	return 0;
}

 预处理了一下前缀和后缀,然后枚举那个不选的数就可以了。O(n)

预处理前i个数的或前缀和还有后i个数的或后缀和

通过前缀和与后缀和的或运算,l[i-1]|r[i+1],可以算出除i这个点的所有数的或和

#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#define pb push_back
#define Rep(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
typedef long long ll;
typedef long double ld;
const int inf=0x3f3f3f3f;
const ll INF=9e18;
const int N=1e6+50;
ll a[N],l[N],r[N];
int main() {
    int n;
    ll ans=0;
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
    for(int i=1;i<=n;i++) l[i]=l[i-1]|a[i];
    for(int i=n;i>=1;i--) r[i]=r[i+1]|a[i];
    for(int i=1;i<=n;i++) ans=max(ans,l[i-1]|r[i+1]);
    printf("%lld\n",ans);
    return 0;
}

 

 

E.小A的路径

链接:https://ac.nowcoder.com/acm/contest/549/E
来源:牛客网
 

题目描述

小A来到了一个陌生的城镇,这个城镇与其它城镇之间构成了集群。城镇之间的路径都是单向的,而小A每一天都能由一个城镇走到另外一个城镇。小A将会连续走k天,直到抵达某个城镇。也许他并不能走到这个城镇,那么可以认为不存在这样的路径,也就是路径数为0。否则就会有若干条路径可以抵达某个城镇。现在他想知道,如果他从给定某个城市出发,k天之后到达其它城镇的路径的总和是多少。数据不保证没有重边,也就是说可能每一天从一个城镇到另外一个城镇之间会有多条路径。路径总和可能会非常大,对答案模上1000000007。

输入描述:

第一行三个整数N,M,K,S分别表示一共有N个城镇,城镇之间有M条单向边。K表示小A连续走K天。S表示小A出发的那个城镇。接下来的M行每行两个整数u,v表示从城镇u连了一条有向边到城镇v。第一行三个整数N,M,K,S分别表示一共有N个城镇,城镇之间有M条单向边。K表示小A连续走K天。S表示小A出发的那个城镇。接下来的M行每行两个整数u,v表示从城镇u连了一条有向边到城镇v。

输出描述:

一行输出一个结果,表示小A到其余城镇路径数的总和。一行输出一个结果,表示小A到其余城镇路径数的总和。

示例1

输入

复制

4 5 2 1
1 2
1 3
2 3
4 1
3 4

输出

复制

2

说明

经过2天,小A可以走到3号城镇或者4号城镇,到3号城镇的路径有一条是1-2-3,到4号城镇的路径也是一条是1-3-4,共计有两条路径。

备注:

 

1≤N≤100, 1≤K≤1e9

 

这题考的是离散数学的内容qaq,点到点的矩阵的几何意义就是能到该点的路径数

矩阵的乘法运算不可避免的O(n^3),幂运算可以用矩阵快速幂优化O(logk),所有总复杂度为O(n^3logk)

 

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;

typedef long long ll;

const ll mod = 1000000007;
const int N = 150;
int n,m,k,s,x,y;

struct Matrix{
    ll a[N][N];
    Matrix() { memset(a,0,sizeof(a)); }
    Matrix operator * (const Matrix &rhs) const{
        Matrix res;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                for(int k=1;k<=n;k++)
                    res.a[i][j] = (res.a[i][j]+a[i][k]*rhs.a[k][j])%mod;

        return  res;
    }
}res,G;

void mpow(int n){
    while(n>0){
        if(n%2==1)
            res=res*G;
        G=G*G;
        n>>=1;
    }
}


int main(){
    scanf("%d%d%d%d",&n,&m,&k,&s);
    for(int i=1;i<=n;i++)
        res.a[i][i] = 1;

    for(int i=1;i<=m;i++){
        scanf("%d%d",&x,&y);
        G.a[x][y]++;
    }


    mpow(k);

    ll ans=0;
    for(int i=1;i<=n;i++)
        if(i!=s)
            ans = (ans+res.a[s][i])%mod;

    printf("%lld\n",ans);

	return 0;
}

 

F.小A的最短路

链接:https://ac.nowcoder.com/acm/contest/549/F
来源:牛客网
 

题目描述

小A这次来到一个景区去旅游,景区里面有N个景点,景点之间有N-1条路径。小A从当前的一个景点移动到下一个景点需要消耗一点的体力值。但是景区里面有两个景点比较特殊,它们之间是可以直接坐观光缆车通过,不需要消耗体力值。而小A不想走太多的路,所以他希望你能够告诉它,从当前的位置出发到他想要去的那个地方,他最少要消耗的体力值是多少。

输入描述:

第一行一个整数N代表景区的个数。
接下来N-1行每行两个整数u,v代表从位置u到v之间有一条路径可以互相到达。
接下来的一行两个整数U,V表示这两个城市之间可以直接坐缆车到达。
接下来一行一个整数Q,表示有Q次询问。
接下来的Q行每行两个整数x,y,代表小A的位置在x,而他想要去的地方是y。

输出描述:

对于每个询问下x,y输出一个结果,代表x到y消耗的最少体力对于每个询问下x,y输出一个结果,代表x到y消耗的最少体力

示例1

输入

复制

4
1 2
1 3
2 4
3 4
2
1 3
3 4

输出

复制

1
0

备注:

1≤N≤3e5, 1≤Q≤1e6

图论,感觉这不是常规的最短路呢,dijkstra,floyd好像都不行( •̀ ω •́ )y

没学lca,贴个代码先

#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
#define pb push_back
#define Rep(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
typedef long long ll;
typedef long double ld;
const int inf=0x3f3f3f3f;
const ll INF=9e18;
const int N=3e5+50;
struct edge{ int v,w; };
vector<edge> G[N];
int n,U,V,dep[N],Fa[N][22];
 
void dfs(int u,int fa) {
    dep[u]=dep[fa]+1;
    Fa[u][0]=fa;
    for(int i=1;(1<<i)<=dep[u];i++) Fa[u][i]=Fa[Fa[u][i-1]][i-1];
    for(auto &e:G[u]) {
        int v=e.v;
        if(v==fa) continue;
        dfs(v,u);
    }
}
int lca(int x,int y) {
    if(dep[x]<dep[y]) swap(x,y);
    for(int i=20;i>=0;i--) if((1<<i)<=dep[x]-dep[y]) x=Fa[x][i];
    if(x==y) return x;
    for(int i=20;i>=0;i--) if(Fa[x][i]!=Fa[y][i]) x=Fa[x][i],y=Fa[y][i];
    return Fa[x][0];
}
int solve(int x,int y) { return dep[x]+dep[y]-2*dep[lca(x,y)]; }
 
struct node{
    int u,dis;
    node() {}
    node(int u,int dis):u(u),dis(dis) {}
    bool operator <(const node &rhs) const {
        return dis>rhs.dis;
    }
};
int dis[N];
priority_queue<node> q;
void spfa() {
    for(int i=1;i<=n;i++) dis[i]=inf;
    dis[U]=0;
    q.push({U,0});
    while(!q.empty()) {
        int u=q.top().u,d=q.top().dis;q.pop();
        if(dis[u]<d) continue;
        for(auto &e:G[u]) {
            int v=e.v,w=e.w;
            if(dis[v]>dis[u]+w) {
                dis[v]=dis[u]+w;
                q.push({v,dis[v]});
            }
        }
    }
}
int main() {
    scanf("%d",&n);
    for(int i=1;i<n;i++) {
        int u,v;
        scanf("%d%d",&u,&v);
        G[u].push_back({v,1});
        G[v].push_back({u,1});
    }
    dfs(1,0);
    scanf("%d%d",&U,&V);
    G[U].push_back({V,0});
    G[V].push_back({U,0});
    spfa();
    int q;
    scanf("%d",&q);
    while(q--) {
        int x,y;
        scanf("%d%d",&x,&y);
        printf("%d\n",min(solve(x,y),dis[x]+dis[y]));
    }
    return 0;
}

G.小A与小B

链接:https://ac.nowcoder.com/acm/contest/549/G
来源:牛客网
 

题目描述

小A与小B这次两个人都被困在了迷宫里面的两个不同的位置,而他们希望能够迅速找到对方,然后再考虑如何逃离迷宫的事情。小A每次可以移动一个位置,而小B每次可以移动两次位置,小A移动的方向是上下左右左上左下右上右下8个方向,小B移动的方向是上下左右4个方向,请问他们最早什么时候能够找到对方,如果他们最终无法相遇,那么就输出”NO"。

输入描述:

第一行两个整数N,M分别表示迷宫的行和列。接下来一个N×M的矩阵其中"C"表示小A的位置,"D"表示小B的的位置,"#"表示不可通过的障碍,"."则是可以正常通过的位置。字符用空格隔开第一行两个整数N,M分别表示迷宫的行和列。接下来一个N×M的矩阵其中"C"表示小A的位置,"D"表示小B的的位置,"#"表示不可通过的障碍,"."则是可以正常通过的位置。字符用空格隔开

输出描述:

如果可以相遇,第一行输出一个YES,第二行一个整数输出最短的相遇时间。
否则就输出一个NO表示不能相遇。

示例1

输入

复制

4 5
. . . . .
. # # # .
. . . # D
. . C # .

输出

复制

YES
3

备注:

1≤n,m≤1000

双向bfs,这题通过率最低,果然大家都是小萌新hah

这题使我对bfs及搜索有了进一步的理解= =

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;


const int N = 1005;

int n,m,g1,g2,g3,g4,id;

struct Node{
    int x,y;
};

char ch[N][N];
bool vis[2][N][N];//0代表A,1代表B
int dx[8] = {1,0,-1,0,1,1,-1,-1};
int dy[8] = {0,1,0,-1,-1,1,1,-1};

queue<Node> q[2];

int bfs(int id){//不循环只做一步的bfs
    int sz = q[id].size();
    while(sz--){//!q[id].empty())会一步执行到维,这里我们只希望它执行一层
        Node u = q[id].front();
        q[id].pop();

        for(int i=0;i<4+(id==0?4:0);i++){
            int x = u.x + dx[i];
            int y = u.y + dy[i];
            if(x>=1 && x<=n && y>=1 && y<=m && ch[x][y]!='#' && !vis[id][x][y] ){
                if(vis[id^1][x][y])
                    return 1;//该点被另一个人访问过了,说明他们在这层可以相遇了

                vis[id][x][y] = true;
                q[id].push({x,y});
            }
        }

    }
    return 0;
}

int solve(){
    q[0].push({g1,g2});
    q[1].push({g3,g4});
    vis[0][g1][g2] = vis[1][g3][g4] = true;
    int step=0;
    while(!q[0].empty() || !q[1].empty()){
        step++;
        if(bfs(0))
            return step;
        if(bfs(1))
            return step;
        if(bfs(1))
            return step;
    }

    return -1;
}


int main(){
    ios::sync_with_stdio(false);
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            cin>>ch[i][j];
            if(ch[i][j]=='C')
                g1=i,g2=j;
            else if(ch[i][j]=='D')
                g3=i,g4=j;
        }

    int ans =solve();
    if(ans==-1)
        cout<<"NO"<<endl;
    else
        cout<<"YES"<<endl<<ans<<endl;

	return 0;
}

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值