发现小白月赛挺好,涉及到很多基础算法,正好复习相关算法
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 x−2∗y 剩下的为没有进位的, 所以 (x - 2 * y) & y == 0 是另一个条件。
- 如果符合条件,输出 x − 2 y x - 2y x−2y 即可
代码:
#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;
}