• 牛客小白月赛#28 题解


    A.牛牛和牛可乐的赌约 #逆元 #快速幂

    题目链接

    题意

    (n)面骰子(点数分别从(1)(n),掷出每面的概率为(frac{1} {n})),先让你投(m)次骰子,如果全部投出点数为(n)点面。你在计算输的概率,请输出分数(p/q mod 1e9+7)

    分析

    显然,P(输)(=1- (frac{1}{n})^m=frac{(n-1)^m}{n^m}),但注意题目要求你求出P(输)在(mod 1e9+7)意义下的值,故先对分子进行快速幂,再对分母(n^m)求出其逆元(inv(n^m))。考虑到模数为质数,直接使用费马小定理+快速幂即可得到逆元。

    费马小定理:若(p)为素数,(a)为正整数,且(a、p)互质,则有(a^{p-1}equiv 1(mod p))

    由于逆元(x)满足$a imes x equiv 1(mod p) Rightarrow a imes x equiv a^{p-1}(mod p) Rightarrow xequiv a^{p-2}(mod p) $

    #include <algorithm>
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <map>
    #include <queue>
    #include <stack>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <cmath>
    #include <unordered_map>
    using namespace std;
    typedef long long ll;
    const int MOD = 1e9 + 7;
    const int MAXN = 55;
    ll qpow(ll base, ll power, ll mymod){
        ll ans = 1;
        while(power > 0){
            if(power & 1)
                ans = ans * base % mymod;
            power >>= 1;
            base = (base * base) % mymod;
        }
        return ans;
    }
    ll Inv(ll a, ll mymod){
        return qpow(a, mymod - (ll)2, mymod);
    }
    int main() {
        int q;
        scanf("%d", &q);
        while(q--){
            ll n, m;
            scanf("%lld%lld", &n, &m);
            ll a = qpow(n, m, (ll)MOD);
            ll b = Inv(a, (ll)MOD);
            printf("%lld
    ", ((ll)(a - 1) * b) % ((ll)MOD));
        }
    }
    
    

    B.牛牛和牛可乐的赌约2 #博弈论 #打表

    题目链接

    题意

    一个棋盘游戏,棋盘左上角为((0,0)),该棋盘在((x, y))的位置有一棋子,你和(Bob)轮流移动该棋子,可左移也可上移,可移动一格或者两格,直到不能在移动(即到达((0,0)))的那个人为输。

    你第一个出手,若游戏过程中两个人都用最优策略,最后你是否能够获胜。

    分析

    感谢@秃头小白的文章讲解。

    显然((0,0))为必败态。而若((x,y))周围((x−1,y)(x−2,y)(x,y−1)(x,y−2))存在一点为必败态,则该点是必胜态。

    for (int i = 1; i <= 45; i++) {
        for (int j = 1; j <= 45; j++) {
            bool ans = 1;
            //寻找必败点
            if (i > 2) ans &= vis[i - 2][j];
            if (j > 2) ans &= vis[i][j - 2];
            if (i > 1) ans &= vis[i - 1][j];
            if (j > 1) ans &= vis[i][j - 1];
            vis[i][j] = !ans;
        }
    }
    for (int i = 1; i <= 45; i++) {
        for (int j = 1; j <= 45; j++) {
            printf("%d ", vis[i][j]);
        }
        printf("
    ");
    }
    

    通过打出P-N表格,我们就能观察本题隐藏的性质,注意到

    该表格对(3 imes 3)矩阵分形,预先处理(3 imes 3)矩阵后,判断询问点在该矩阵位置即可。

    #include <algorithm>
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <map>
    #include <queue>
    #include <stack>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <cmath>
    #include <unordered_map>
    using namespace std;
    typedef long long ll;
    const int MOD = 1e9 + 7;
    const int MAXN = 55;
    string str;
    bool vis[MAXN][MAXN];
    bool ans[4][4] = {{0, 1, 1}, {1, 0, 1}, {1, 1, 0}};
    int main() {
        int q;
        scanf("%d", &q);
        while(q--){
            int i, j; scanf("%d%d", &i, &j);
            if(ans[i % 3][j % 3]) printf("yyds
    ");
            else printf("awsl
    ");
        }
    }
    

    C.单词记忆方法 #递归 #分治

    题目链接

    题意

    每个字母都有一个权值,(A)权值为(1)(B)权值为(2),… 依次类推。现要你求出一个字符串所有字母的权值总和,注意该字符串存在括号及数字,如(ABCABC、(ABC)2、HHHH、(H2)2、H4),形如化学式。

    分析

    其实这题就是CCF CSP201912-3的化学方程式的弱化版,只不过你不用判断等式两边是否相等,只需考虑一个化学式中有多少个原子即可。

    #include <algorithm>
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <map>
    #include <queue>
    #include <stack>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <cmath>
    #include <unordered_map>
    using namespace std;
    string str;
    ll vis[4], ans;
    ll getDigit(int& lo, int hi){
        ll res = 0;
        while(lo <= hi && isdigit(str[lo]))
            res = res * (ll)10 + (ll)(str[lo++] - '0');
        return res == 0 ? 1 : res;
    }
    void Compute(int lo, int hi, ll val){
        if(lo == hi){
            ans += (val * (ll)(str[lo] - 'A' + 1));
            return;
        }
        val *= (ll)getDigit(lo, hi);
        for (int i = lo, j = i + 1; i <= hi; i = j, j++){
            if('A' <= str[i] && str[i] <= 'Z'){
                int tmp = j;
                Compute(i, j - 1, val * (ll)getDigit(tmp, hi));
            }
            else if(str[i] == '('){
                for (int cnt = 1; cnt != 0; j++){
                    if(str[j] == '(') cnt++;
                    else if(str[j] == ')') cnt--;
                }
                int tmp = j;
                Compute(i + 1, j - 1, val * (ll)getDigit(tmp, hi));
            }
        }
    }
    int main(){
        cin >> str;
        Compute(0, str.length(), 1);
        printf("%lld
    ", ans);
    }
    

    D.位运算之谜

    题目链接

    题意

    (a+b)的值为(x)(a&b)的值为(y),首先需要判断能否有一组(a, b)满足当前的情况,如果有,那么求出(a xor b),否则输出(-1)

    分析

    实际上该题并不需要我们将(a,b)的具体值求出来,我们知道异或和即是不进位的加法,我们现在知道(a+b)的结果,又因为((a&b)<<1)模拟(a+b)的进位。正常的加法运算 (a+b = a xor b+((a&b)<<1))

    (a+b - ((a & b ) << 1))如果是负数,显然无法存在(a,b)满足题意,因为(a xor b)一定不为负。另外,还有一个排除条件,由于(a&b)(a xor b)一定取反,也就说两者按位与一定为(0)

    #include <algorithm>
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <map>
    #include <queue>
    #include <stack>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <cmath>
    #include <unordered_map>
    using namespace std;
    typedef long long ll;
    ll x, y;
    int main(){
        int q; scanf("%d", &q);
        while(q--){
            scanf("%lld%lld", &x, &y);
            ll ans = x - (ll)(y << 1);
            if(ans < 0 || ans & y) printf("-1
    ");
            else if(ans >= 0) printf("%lld
    ", ans);
        }
    }
    

    F.牛牛的健身运动 #三分思想

    题目链接

    题意

    (m)天中,牛牛选其中一天来锻炼。每天有(n)件器材。第(k)天的第(i)种器材能够增加(k*a_i+b_i)力量。由于某种原因,牛牛只能在当天选择所有器材中增加力量值最小的两件(且两件不能相同)。牛牛希望能够选择某一天,使得他增加力量值最大,现要你求出他最多增加的力量。

    分析

    每一天牛牛只能选择力量值最小的两件,也就说假定现在为第(k)天,则他只能增加(y_k=min(k*(a_i+a_j)+(b_i+b_j), 1leq i,j leq n))力量值,显然需要枚举两两器材搭配取得这一最小值。

    但牛牛又希望从所有天中的最小值(y_k(1leq k leq m))取得最大值,求给定区间的最大值,用三分查找的算法较为适合。

    #include <string>
    #include <cstring>
    #include <cstdio>
    #include <iostream>
    #include <stack>
    #include <queue>
    #include <map>
    #include <vector>
    #include <deque>
    #include <cmath>
    #include <algorithm>
    #include <unordered_map>
    using namespace std;
    typedef long long ll;
    const int MAXN = 2e3 + 5;
    int n, m;
    ll a[MAXN], b[MAXN];
    ll Check(int x) { //获得当天增加力量最小值
        ll mymin = 0x3f3f3f3f3f3f3f3f;
        for (int i = 1; i <= n; i++)
            for (int j = i + 1; j <= n; j++) 
                mymin = min(mymin, (a[i] + a[j]) * (ll)x + (b[i] + b[j]));
        return mymin;
    }
    int main() {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; i++) scanf("%lld%lld", &a[i], &b[i]);
        int lo = 1, hi = m;
        ll ans = -0x3f3f3f3f3f3f3f3f;
        while (lo + 10 < hi) { //缩小区间
            int mid1 = lo + (hi - lo) / 3;
            int mid2 = hi - (hi - lo) / 3;
            if (Check(mid1) <= Check(mid2)) lo = mid1;
            else hi = mid2;
        }
        for (int i = lo; i <= hi; i++) ans = max(ans, Check(i)); //在小区间枚举比较最值
        printf("%lld
    ", ans);
        return 0;
    }
    

    G.牛牛和字符串的日常 #KMP

    题目链接

    题意

    给定喜欢句子,如果今日读的书中某连续(k)个字符恰好为喜欢句子的某个前缀,那么能够加(k)分。然而每天只能注意到一次自己喜欢的句子(即只能加分一次),你需要尽可能找到加分高的句子。现要求(n)天后总共最多能有多少分

    分析

    由数据规模可知只能满足(O(n))或更低的算法。直接用KMP模板即可,求出模式串(即喜欢句子)最长匹配长度。

    #include <algorithm>
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <map>
    #include <queue>
    #include <stack>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <cmath>
    #include <unordered_map>
    using namespace std;
    typedef long long ll;
    const int MOD = 1e9 + 7;
    const int MAXN = 1e5 + 5;
    int nextTable[MAXN];
    void buildNext(string str){
        int comlen = nextTable[0] = -1;
        int len = str.length(), i = 0;
        while(i < (len - 1)){
            if(comlen == -1 || str[i] == str[comlen]){
                i++;
                comlen++;
                nextTable[i] = comlen;
            }
            else comlen = nextTable[comlen];
        }
    }
    int KMP(string str, string T){
        int i = 0, j = 0;
        int res = -1;
        while(i < (int)T.length() && j < (int)str.length()){
            if(j < 0 || T[i] == str[j]){
                i++; j++;
            }
            else j = nextTable[j];
            res = max(res, j); //需添加的语句,更新模式串最长匹配长度
        }
        return res;
    }
    int main(){
        string str; cin >> str; buildNext(str);
        int n, ans = 0; scanf("%d", &n);
        for (int i = 1; i <= n; i++){
            string tmp; cin >> tmp;
            ans += KMP(str, tmp);
        }
        printf("%d
    ", ans);
    }
    

    H.上学要迟到了 #单源最短路径

    题目链接

    题意

    你去学校的路是一条总共有(n)个公交车站的直线,且车站之间距离相等,每个车站只有一种对应的公交车(a_i),且每个公交车只在对应站停下。而第(i)种公交车到达其下一个对应车站所需时间为(t_i),且单向行驶(从左到右)。而走路可以任意方向走,但步行走一站需(T)时间。你的家在(s),学校在(t),求到达学校最短时间。

    分析

    这题其实不难,但是要注意两个隐藏细节(wa了好多发):

    • 虽然公交车是单向,但人步行可以双向!
    • 公交车从一个对应站到另一个对应站,无论距离多长,耗费时间固定!

    接下来,给人步行路径建双向边,给每种车的途径站建单向边,用(Dijkstra)算法求出路径即可。

    #include <algorithm>
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <map>
    #include <queue>
    #include <stack>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <cmath>
    #include <unordered_map>
    using namespace std;
    typedef long long ll;
    const int MOD = 1e9 + 7;
    const int MAXN = 1e5 + 5;
    typedef struct Edge{
        int to, w;
    } Edge;
    typedef struct qnode{
        int v, data;
        bool operator <(const qnode& r) const {
            return data > r.data;
        }
    }qnode;
    vector<Edge> H[MAXN]; 
    int n, m, st, ed, t;
    int cost[MAXN], lastPos[MAXN];
    int dis[MAXN];
    void dijkstra(){
        priority_queue<qnode> myque;
        for (int i = 1; i <= n; i++) dis[i] = 0x3f3f3f3f;
        dis[st] = 0;
        myque.push({st, 0});
        while(!myque.empty()){
            qnode tmp = myque.top(); myque.pop();
            int u = tmp.v, len = tmp.data;
            if(len != dis[u]) continue;
            for (int i = 0; i < H[u].size(); i++){
                int v = H[u][i].to, w = H[u][i].w;
                if(dis[u] + w < dis[v]){
                    dis[v] = dis[u] + w;
                    myque.push({v, dis[v]});
                }
            }
        }
    }
    int main(){
        cin >> n >> m >> st >> ed >> t;
        for (int i = 1; i <= m; i++) scanf("%d", &cost[i]);
        for(int i = 1, v; i <= n; i++){
            scanf("%d", &v);
            if(lastPos[v] != 0) H[lastPos[v]].push_back({i, cost[v]}); 
            lastPos[v] = i;//记录v车的上一站点
        }
        for (int i = 1; i <= n - 1; i++) H[i].push_back({i + 1, t});
        for (int i = n; i >= 2; i--) H[i].push_back({i - 1, t}); //双向边
        dijkstra();
        printf("%d
    ", dis[ed]);
        return 0;
    }
    
    
    

    I.迷宫 #动态规划

    题目链接

    题意

    (n imes m(n,mleq 100))地图,每个点有权值(a_{ij}),现要你从((1,1))走到((n, m)),可以往右走或往下走,每经过一个点便可得到相应权值,对权值和(mod 1e4+7)。问你从不同方式走到((n,m))能获得多少权值和?

    分析

    如果暴力搜索的话,总共有(C^n_{m+n})路径,估计时间复杂度为(O(n!)),显然要用DP。

    注意到取模(1e4+7),我们知道权值和最大可能(1e4+7),最小可能(1),说明我们可以把权值枚举出来的。但是我们不知道能取到这一区间的哪些值,所以需要DP下看看哪些模数对应的权值和是可达的。

    于是定义(dp[i][j][k])表示,地图((i,j))点,能否达到权值(k)。接下来枚举地图上的每一点及其所有模数,转移可从上一行的点、上一列的点进行。

    #include <algorithm>
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <map>
    #include <queue>
    #include <stack>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <cmath>
    #include <unordered_map>
    using namespace std;
    typedef long long ll;
    const int MOD = 1e4 + 7;
    const int MAXN = 105;
    int n, m, val[MAXN][MAXN];
    bool dp[MAXN][MAXN][MOD + 5];
    int main(){
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; i++){
            for (int j = 1; j <= m; j++){
                int tmp; scanf("%d", &tmp);
                val[i][j] = tmp % MOD; //避免
            }
        }
        dp[1][1][val[1][1]] = 1;
        for (int i = 1; i <= n; i++){
            for (int j = 1; j <= m; j++){
                for (int k = 0; k < MOD; k++){ //枚举模数
                    dp[i][j][k] |= dp[i - 1][j][((k - val[i][j]) % MOD + MOD) % MOD];
                    dp[i][j][k] |= dp[i][j - 1][((k - val[i][j]) % MOD + MOD) % MOD];
                }
            }
        }
        int ans = 0;
        for (int k = 0; k < MOD; k++) ans += dp[n][m][k];
        printf("%d
    ", ans);
    }
    

    J.树上行走 #并查集

    题目链接

    题意

    给定(n)个节点、(n-1)条边的树,它有两种类型的点,一旦进入一个点,你只能走到与之有边相连的且与之同类型节点,现要你尽量在更多的节点可以移动,可以进入哪些点?

    分析

    先对同类型节点进行合并成树,然后对每种类型节点的树的规模进行比较,找到其最值,最后再对规模等于该最值的类型节点加入答案。

    #include <algorithm>
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <map>
    #include <queue>
    #include <stack>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <cmath>
    #include <unordered_map>
    using namespace std;
    typedef long long ll;
    const int MOD = 1e9 + 7;
    const int MAXN = 2e5 + 5;
    int flag[MAXN], tot = 0, fa[MAXN];
    int n, sz[MAXN], ans[MAXN];
    int findSet(int x){
        if(x != fa[x]) fa[x] = findSet(fa[x]);
        return fa[x];
    }
    int main(){
        scanf("%d", &n);
        for (int i = 1, tmp; i <= n; i++){
            scanf("%d", &flag[i]);
            fa[i] = i;
        }
        for (int i = 1, u, v; i <= n - 1; i++){
            scanf("%d%d", &u, &v);
            if(flag[u] != flag[v]) continue; 
            int pu = findSet(u), pv = findSet(v);
            fa[pu] = pv; //同类型节点进行合并
        }
        int mymax = -1, cnt = 0;
        for (int i = 1; i <= n; i++) findSet(i); //一定要保证所有顶点的路径都压缩完!!!
        for (int i = 1; i <= n; i++){
            sz[fa[i]]++; //统计该类型节点组成的树规模
            mymax = max(mymax, sz[fa[i]]);
        }
        for (int i = 1; i <= n; i++){
            if(sz[fa[i]] == mymax) ans[++cnt] = i; //满足条件,加入答案
        }
        printf("%d
    ", cnt);
        for (int i = 1; i <= cnt; i++) printf("%d ", ans[i]);
        return 0;
    }
    
  • 相关阅读:
    Snmpwalk
    本地使用PostMan测试接口没有问题,但是线上直接进不了接口
    redis使用注意事项
    线程笔记
    springboot笔记
    springboot中多个地址指向同一个方法
    开发过程中遇到的注解
    使用maven时用到的一些问题
    遇到线程安全问题的案例
    mongodb
  • 原文地址:https://www.cnblogs.com/J-StrawHat/p/13737446.html
Copyright © 2020-2023  润新知