牛客 — 小白月赛28

发现小白月赛挺好,涉及到很多基础算法,正好复习相关算法

传送门

A. 牛牛和牛可乐的赌约

主要思路: 这题就是一个很基础的求概率的题,用到了逆元(模板题),只要会逆元,就可以轻松解决。

解题思路:
  • 首先每次投出n的概率都是 1 / n 1/n 1/n,所以投出 m 次 n 的概率就是 1 / n m 1/n^m 1/nm
  • 所以,牛牛输的概率为 1 - 1 / n m 1/n^m 1/nm, 很显然 1 / n m 1/n^m 1/nm 用到了逆元,直接带入求结果即可。
  • 另外一点就是,exp求的时候如果乘积也算进去,会变的很慢(TLE)。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <unordered_map>
#include <stack>
#include <cmath>
#include <deque>

using namespace std;

typedef long long ll;

const int N = 100010, M = 200010;
const ll mod = 1e9 + 7;

ll mod_exp(ll a,ll b,ll m){
    ll res = 1;
    ll exp = a % m;
    while(b){
        if (b & 1) res = res * exp % m;
        exp = exp * exp % m;
        b>>=1;
    }
    return res;
}

int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        ll n, m;
        scanf("%lld%lld",&n,&m);
        ll x = mod_exp(n,m,mod);
        printf("%lld\n",(mod + 1 - mod_exp(x,mod-2,mod)) % mod);
    }
    return 0;
}


B. 牛牛和牛可乐的赌约2

主要思路:博弈题,主要是找规律,这里也是看了相关题解学会的,需要打表看下规律,之后就很简单了。

解题思路:
  • 首先我们要考虑什么时候牛牛会赢,我们先以3 * 3 的表为例(后面通过打表,发现3 * 3 是最小单位)
    在这里插入图片描述
  • 其中,叉号表示牛牛必败,对勾表示必胜。
  • 然后通过打表发现,3 * 3 是最小子单元,因此考虑3 * 3 即可。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <unordered_map>
#include <stack>
#include <cmath>
#include <deque>

using namespace std;

typedef long long ll;

const int N = 100010, M = 200010;
const int mod = 1e9 + 7;

int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        int x, y;
        scanf("%d%d",&x,&y);
        x %= 3, y %= 3;
        if (x == y){
            puts("awsl");
        }
        else puts("yyds");
    }
    return 0;
}


C. 单词记忆方法

主要思路:字符串模拟题,主要是找好括号的层级,然后进行存储,最后算出最后层级的数目即可。

解题思路:
  • 首先分为几种情况:左括号、右括号、数字
  • 左括号: 如果遇到左括号,那么就直接tp[++x] = 0,让当前层的数值为0,因为这是这一层的新括号,初始化一下。
  • 右括号:我们之间找右括号右面的数字,如果无,那么当1处理,如果有就计算出,然后计算当前层的数值,然后加到上一层括号中(因为这层括号肯定是包含在上一层括号当中的),然后对x–(减少层次)
  • 遇到字母,直接进行计算即可。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <unordered_map>
#include <stack>
#include <cmath>
#include <deque>

using namespace std;

typedef long long ll;

const int N = 100010, M = 200010;
const int mod = 1e9 + 7;

char st[N];
ll tp[N];

int main(){
    scanf("%s",st);
    int n = strlen(st);
    int x = 0;
    for (int i = 0; i < n; i ++){
        if (st[i] == '('){
            tp[++x] = 0;
        }
        else if (st[i] == ')'){
            ll num = 0;
            while(st[i + 1] >= '0' && st[i + 1] <= '9' && i + 1 < n){
                num = num * 10 + (st[i + 1] - '0');
                i ++;
            }
            if (num <= 0) num = 1;
            tp[x] = num * tp[x];
            tp[x - 1] += tp[x];
            x --;
        }
        else if (st[i] >= 'A' && st[i] <= 'Z'){
            ll now = st[i] - 'A' + 1;
            ll num = 0;
            while(st[i + 1] >= '0' && st[i + 1] <= '9' && i + 1 < n){
                num = num * 10 + (st[i + 1] - '0');
                i ++;
            }
            if (num <= 0) num = 1;
            tp[x] += num * now;
        }
    }
    printf("%lld\n",tp[x]);
    return 0;
}


D. 位运算之谜

主要思路: 数学推导题,根据 xor 与 & 等性质进行推导

解题思路:
  • 首先 a + b = x a+b=x a+b=x a a a & b = y b = y b=y , 这里 a & b 相同为 1,a + b,中有a & b 的进位,所以要想条件成立,x - 2 * y >= 0
  • 其次, x − 2 ∗ y x - 2* y x2y 剩下的为没有进位的, 所以 (x - 2 * y) & y == 0 是另一个条件。
  • 如果符合条件,输出 x − 2 y x - 2y x2y 即可
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <unordered_map>
#include <stack>
#include <cmath>
#include <deque>

using namespace std;

typedef long long ll;

const int N = 100010, M = 200010;
const int mod = 1e9 + 7;

int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        ll x, y;
        scanf("%lld%lld",&x,&y);
        if (x >= 2 * y && (((x - 2 * y) & y) == 0)) printf("%lld\n",x - 2 * y);
        else puts("-1");
    }
    return 0;
}




G. 牛牛和字符串的日常

主要思路:KMP模板题,很简单

解题思路:
  • 搬上KMP模板
  • 然后计算每个匹配的最大数目,累加即可。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <unordered_map>
#include <stack>
#include <cmath>
#include <deque>

using namespace std;

typedef long long ll;

const int N = 100010, M = 200010;
const int mod = 1e9 + 7;

char p[N], s[N];
int ne[N], n;

void next_(){
    for (int i = 2, j = 0; i <= n; i ++){
        while(j && p[i] != p[j + 1]) j = ne[j];
        if (p[i] == p[j + 1]) j ++;
        ne[i] = j;
    }
}

int main(){
    scanf("%s", p + 1);
    n = strlen(p + 1);
    next_();
    int t;
    scanf("%d",&t);
    int res = 0;
    for (int l = 1; l <= t; l ++){
        scanf("%s",s + 1);
        int mx = 0;
        int m = strlen(s + 1);
        for (int i = 1, j = 0; i <= m; i ++){
            while(j && s[i] != p[j + 1]) j = ne[j];
            if (s[i] == p[j + 1]) j ++;
            mx = max(mx,j);
            if (j == m){
                break;
            }
        }
        res += mx;
    }    
    printf("%d\n",res);
    return 0;
}


H. 上学要迟到了

主要思路: 最短路问题,难点就是如何建图。方式分为多种,步行去上一个站点,步行去下一个站点,坐车去下一个站点。步行的话就是相邻两边建边,权值为T, 坐车根据2个相邻点进行选取。建完图直接跑dijkstra即可。

解题思路:
  • 首先进行初始化,然后开始存图。
  • 首先我们判断节点是否符合要求,如果是第一个节点,那么没有前续节点,如果是最后一个节点,就没有后续节点,如果存在当前公交车经过的站点,那么进行存边(所以最高可以达到3e5,注意数组的大小)
  • 然后进行最短路算法即可,最后输出dist[t]即可。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <unordered_map>
#include <stack>
#include <cmath>
#include <deque>

using namespace std;

typedef long long ll;

const int N = 200010, M = 400010;
const int mod = 1e9 + 7;

int a[N],sum[N],st[N],val[M];
int h[M],ne[M],dist[N],e[M],w[M];

bool vis[M];

int idx = 0;

void add(int x,int y, int z){
    e[idx] = y, ne[idx] = h[x], w[idx] = z, h[x] = idx++;
}

void dfs(int s){
    queue<int> q;
    vis[s] = 1;
    dist[s] = 0;
    q.push(s);
    while(!q.empty()){
        int k = q.front();
        q.pop();
        for (int i = h[k]; i != -1; i = ne[i]){
            int j = e[i];
            if (dist[j] > dist[k] + w[i]){
                dist[j] = dist[k] + w[i];
                if (!vis[j]){
                    vis[j] = 1;
                    q.push(j);
                }
            }
        }
        vis[k] = 0;
    }
}

int main(){
    memset(h,-1,sizeof h);
    memset(dist,0x3f,sizeof dist);
    int n, m, s, t, T;
    scanf("%d%d%d%d%d",&n,&m,&s,&t,&T);
    for (int i = 1; i <= m; i ++){
        scanf("%d",&sum[i]);
    }
    for (int i = 1; i <= n; i ++){
        scanf("%d",&a[i]);
    }
    for (int i = 1; i <= n; i++){
        if (i > 1) add(i, i - 1, T);
        if (i < n) add(i, i + 1, T);
        if (st[a[i]]) add(st[a[i]],i,sum[a[i]]);
        st[a[i]] = i;
    }

    dfs(s);

    printf("%d\n",dist[t]);
    return 0;
}


I. 迷宫

主要思路: DP,主要是自己dp不太会,每次看到都有点慌,不过这次还好。主要找从1,1到最后n,m 点 有多少不同的权值可以达到,我们直接三维数组进行存储,st[i][j][k] st[i][j] 代表位置,[k] 代表当前的权值,这里只要判断当前的st是否为1即可,最后累加

解题思路:
  • 首先只能向下或者向右,那么就是由上和左来进行状态转移,当前的状态st[i][j][k], k 值是否能达到,只需要看左、上点所积累的权值 + 当前的权值是否能达到即可,因为这里不知道具体权值的数目,因此遍历求取。
 	for (int k = 0; k < mod ;k ++){
	    st[i][j][(a[i][j] + k)%mod] |= st[i-1][j][k];
	    st[i][j][(a[i][j] + k)%mod] |= st[i][j - 1][k];
	}
  • 最后只判断n,m 点存在多少不同的权值点即可:
	for (int i = 0; i < mod; i ++){
        if (st[n][m][i]) res ++;
    }
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <unordered_map>
#include <stack>
#include <cmath>
#include <deque>

using namespace std;

typedef long long ll;

const int N = 100010, M = 200010;
const int mod = 1e4 + 7;

bool st[110][110][10010];

int a[110][110];

int main(){
    int n, m;
    scanf("%d%d",&n,&m);
    
    for (int i = 1; i <= n; i ++){
        for (int j = 1; j <= m; j++){
            scanf("%d",&a[i][j]);
            a[i][j] %=mod;
        }
    }
    st[1][1][a[1][1]] = 1;
    for (int i = 1; i <= n; i ++){
        for (int j = 1; j <= m; j ++){
            if (i == 1 && j == 1) continue;
            for (int k = 0; k < mod ;k ++){
                st[i][j][(a[i][j] + k)%mod] |= st[i-1][j][k];
                st[i][j][(a[i][j] + k)%mod] |= st[i][j - 1][k];
            }
        }
    }
    int res = 0;
    for (int i = 0; i < mod; i ++){
        if (st[n][m][i]) res ++;
    }
    printf("%d\n",res);
    return 0;
}


J. 树上行走

解题思路:并查集,很奇妙。 将相同的点的有边的连在一起,然后判断点的多少即可。

解题思路:
  • 首先初始化(很重要),然后输入边,如果边上两点的类型相同,那么进行合并。
  • 然后寻找最大的部落集合(数目最多)
  • 进行存储于统计数目,然后输出即可
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <unordered_map>
#include <stack>
#include <cmath>
#include <deque>

using namespace std;

typedef long long ll;

const int N = 200010, M = 200010;
const int mod = 1e9 + 7;


int p[N],a[N],b[N];

int Find(int x){  // 路径压缩
    if (p[x] != x ) p[x] = Find(p[x]);
    return p[x];
}

int main(){
    int n;
    scanf("%d",&n);
    for (int i = 1; i <= n; i++){
        scanf("%d",&a[i]);
        p[i] = i;
    }
    for (int i = 1; i < n; i++){
        int x, y;
        scanf("%d%d",&x,&y);
        if (a[x] == a[y]){
            p[Find(x)] = Find(y);
        }
    }
    int mx = 0;
    for (int i = 1; i <= n; i ++){
        b[Find(i)] ++;
        mx = max(b[Find(i)],mx);
    }
    int ans = 0;
    for (int i = 1; i <= n ; i++){
        if (b[Find(i)] == mx){
            ans ++;
        }
    }
    printf("%d\n",ans);
    for (int i = 1; i <= n ; i++){
        if (b[Find(i)] == mx){
            printf("%d ",i);
        }
    }
    puts("");
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值