牛客小白月赛#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;
}
原文地址:https://www.cnblogs.com/J-StrawHat/p/13737446.html