[补题] 牛客小白月赛13

先把自己做出来的题目发出来
A.小A的签到题
这道题,真的傻乎乎的直接把代码提交上去了。。。果不其然,没过。
嗯,怎么做呢?只需要把代码放在文件里,然后尝试几个数据就知道答案了。

#include
using namespace std;
int main() {
    long long n;
    scanf("%lld",&n);
    if (n%2 == 0) printf("-1");
    else printf("1");
    return 0;
}

B.小A的回文串
这是一个循环的回文串。

#include 
#include 
#include 

using namespace std;

const int MAXN = 5005;
char s[MAXN];

int main () {
	scanf("%s", s);
	int len = strlen(s), ans = 0;
	for (int i = 0; i < len; i++) {
		int cnt = -1;
		for (int a = i, b = i; s[a] == s[b]; a = (a-1+len)%len, b = (b+1)%len) {
			if (cnt > len - 2) break;
			cnt = cnt+2;
		}
		ans = max(ans, cnt);
	}
	for (int i = 0; i < len; i++) {
		int cnt = 0;
		for (int a = (i-1+len)%len, b = i; s[a] == s[b]; a = (a-1+len)%len, b = (b+1)%len) {
			if (cnt > len - 2) break;
			cnt = cnt+2;
		}
		ans = max(ans, cnt);
	}
	printf("%d", ans);
	return 0;
}

C. 小A买彩票
嗯,这是一道数学题。先说一个可以用到的公式:
在这里插入图片描述
因为n<=30可以先把所有的组合数写出来,然后在三重循环d张4元,c张3元,b张2元,把所有大于等于3*n的可能情况加起来,再除以所有可能的情况4^n。分子和分母的公约数只有可能是2的次方。

#include 
#include 

using namespace std;

const int MAXN = 35;

unsigned long long C[MAXN][MAXN];

int main () {
	int n;
	scanf("%d", &n);
	for (int i = 0; i <= n; i++) {
		C[i][0] = 1;
		C[0][i] = 1;
	} 
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= i; j++) {
			C[j][i] = ((i-j+1) * C[j-1][i]) / j;
		}
	}
	unsigned long long fenzi = 0;
	for (int d = n; d >= 0; d--) {
		for (int c = n-d; c >= 0; c--) {
			for (int b = n-d-c; b >= 0; b--) {
				int a = n-d-c-b;
				if (d*4+c*3+b*2+a >= n*3) {
					fenzi += C[d][n]*C[c][n-d]*C[b][n-d-c];
				}
			}
		}
	}
	n = 2*n;
	while (fenzi%2 == 0) {
		fenzi = fenzi/2;
		n--;
	}
	unsigned long long fenmu = pow(2, n);
	printf("%llu/%llu", fenzi, fenmu);
	return 0;
}

D. 小A的位运算

#include 
#include 

using namespace std;

const int MAXN = 5000010;
int N, p[MAXN], L[MAXN], R[MAXN];

int main () {
	scanf("%d", &N);
	int minV = 1e9; 
	for (int i = 0; i < N; i++) {
		scanf("%d", &p[i]);
	}
	for (int i = 1; i < N; i++) {
		L[i] = L[i-1] | p[i-1];
	}
	for (int i = N-2; i >= 0; i--) {
		R[i] = R[i+1] | p[i+1];
	}
	int ans = 0;
	for (int i = 0; i < N; i++) {
		ans = max(ans, L[i] | R[i]);
	}
	printf("%d", ans);
	return 0;
}

H.小A的柱状图
这道题,竟然暴力可解,果然是数据太水了。。。

#include 
#include 

using namespace std;

const int MAXN = 1e6+10;

unsigned long long A[MAXN], H[MAXN];
int n;

int main () {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) {
		scanf("%llu", &A[i]);
	}
	for (int i = 1; i <= n; i++) {
		scanf("%llu", &H[i]);
	}
	unsigned long long ans = 0;
	for (int i = 1; i <= n; i++) {
		int w = A[i];
		for (int j = i-1; H[j] >= H[i] && j >= 1; j--) {
			w += A[j];
		}
		for (int j = i+1; H[j] >= H[i] && j <= n; j++) {
			w += A[j];
		}
		ans = max(ans, w * H[i]);
	}
	printf("%llu", ans);
	return 0;
}

嗯,但是我还是需要学习一下更加精妙的解法,也就是单调栈。
代码如下:

#include 
#include 
#include 

#define LL long long

using namespace std;


const int MAXN = 1e6+10;

LL W[MAXN], H[MAXN], L[MAXN], R[MAXN]; 
//W[i]为从0到i的宽度,L[i]为向左扩展的最大宽度,直至遇到比H[i]小,R[i]为向右扩展直至遇到比H[i]小的 
int n;

int main () {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) {
		scanf("%llu", &W[i]);
		W[i] += W[i-1];
	}
	for (int i = 1; i <= n; i++) {
		scanf("%llu", &H[i]);
	}
	stack  sh, sw;
	sh.push(-1); sw.push(0); // 防止溢出
	for (int i = 1; i <= n; i++) {
		while (sh.top() >= H[i]) {
			sh.pop(); sw.pop();
		}
		L[i] = sw.top();
		sh.push(H[i]); sw.push(W[i]);
	} 
	sh.push(-1); sw.push(W[n]);
	for (int i = n; i >= 1; i--) {
		while (sh.top() >= H[i]) {
			sh.pop(); sw.pop();
		}
		R[i] = sw.top();
		sh.push(H[i]); sw.push(W[i-1]);
	}
	LL ans = 0;
	for (int i = 1; i <= n; i++) {
		ans = max(ans, (R[i] - L[i]) * H[i]);
	}
	printf("%llu", ans);
	return 0;
}

以上就是我做出来的五道题,下面就是我需要补的题。

E.小A的路径
这道题离散数学学过,并且用到矩阵快速幂,可惜的是我没学快速幂,并且当时也根本没有想到离散数学。
先记录一下快速幂的模板:

int pow2(int a, int b) {
    int ans = 1, base = a;
    while (b != 0) {
        if (b & 1 != 0) {
        	ans *= base;
		}
        base *= base;
        b >>= 1;
    }
    return ans;
}

矩阵快速幂的模板:

#include 

using namespace std;

#define LL long long

const LL MOD = 1000000007;
const int MAXN = 105;
struct Matrix {
	LL m[MAXN][MAXN];
}; 
Matrix m;
LL k;
int n;

// 矩阵的乘法 
Matrix mul (Matrix a, Matrix b) {
	Matrix c;
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			c.m[i][j] = 0;
		}
	}
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			for (int k = 0; k < n; k++) {
				c.m[i][j] = c.m[i][j]  % MOD + a.m[i][k]*b.m[k][j] % MOD;
			}
		}
	}
	return c;
}

// 矩阵的快速幂 
Matrix pow2 (Matrix a) {
	Matrix ans;
	for (int i = 0; i < n; i++) {
		ans.m[i][i] = 1;
	}
	while (k != 0) {
		if ((k&1) != 0) {
			ans = mul(ans, a);
		}
		a = mul(a, a);
		k = k >> 1;
	}
	return ans;
}

int main () {
	scanf("%d %llu", &n, &k);
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			scanf("%llu", &m.m[i][j]);
		}
	}
	Matrix ans = pow2(m);
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			if (j != 0) printf(" ");
			printf("%lld", (ans.m[i][j])%MOD);
		}
		printf("\n");
	}
	return 0;
}

小白月赛E题的AC代码如下:

#include 

using namespace std;
#define ULL unsigned long long

const int MAXN = 105;
const int MOD = 1000000007;

struct Matrix {
	ULL m[MAXN][MAXN];
};

int N, M, K, S;

Matrix mul (Matrix a, Matrix b) {
	Matrix c;
	for (int i = 1; i <= N; i++) {
		for (int j = 1; j <= N; j++) {
			c.m[i][j] = 0;
			for (int k = 1; k <= N; k++) {
				c.m[i][j] = c.m[i][j] % MOD + a.m[i][k] * b.m[k][j] % MOD;
			}
		}
	}
	return c;
}

Matrix pow2 (Matrix a) {
	Matrix ans;
	for (int i = 1; i <= N; i++) {
		for (int j = 1; j <= N; j++) {
			if (i == j) ans.m[i][i] = 1;
			else ans.m[i][j] = 0;
		}
	}
	while (K) {
		if (K&1) {
			ans = mul(ans, a);
		}
		a = mul(a, a);
		K = K >> 1;
	}
	return ans;
}

int main () {
	scanf("%d %d %d %d", &N, &M, &K, &S);
	Matrix a;
	for (int i = 1; i <= N; i++) {
		for (int j = 1; j <= N; j++) {
			a.m[i][j] = 0;
		}
	}
	for (int i = 0, u, v; i < M; i++) {
		scanf("%d%d", &u, &v);
		a.m[u][v]++;
	}
	Matrix m = pow2(a);
	ULL ans = 0;
	for (int i = 1; i <= N; i++) {
		if (i != S) ans = (ans + m.m[S][i]) % MOD;
	}
	printf("%llu", ans % MOD);
	return 0;
}

F. 小A的最短路
这是一颗树,然后再加上了一条长度为0的边。可以把他分成两种情况,一种是走长度为0的边,一种是不走长度为0的边。
如果不走长度为0的边,那么x和y之间的距离就是: depth[x] + depth[y] - 2 * depth[ lca[x][y]].
如果走长度为0的边,x与y之间的距离,就等dis(x->u) + dis(y->u)
我的AC代码:

#include 
#include 
#include 

using namespace std;

const int MAXN = 300005;
int depth[MAXN], n, dis[MAXN], U, V, Q;
vector edge[MAXN], father[MAXN]; // edge存储边,father存储祖先元素 

void build (int index, int f) {
	depth[index] = depth[f] + 1;
	father[index].push_back(index);
	for (int j = 0; j < father[f].size(); j++) {
		father[index].push_back(father[f][j]);
	}
	for (int i = 0; i < edge[index].size(); i++) {
		int u = edge[index][i];
		if (depth[u] == 0) build(u, index);
	}
}

void bfs () {
	queue q;
	q.push(U); q.push(V);
	dis[U] = 1; dis[V] = 1;
	while (!q.empty()) {
		int index = q.front(); q.pop();
		for (int i = 0; i < edge[index].size(); i++) {
			int u = edge[index][i];
			if (dis[u] != 0) continue;
			dis[u] = dis[index] + 1;
			q.push(u);
		}
	} 
}

int main () {
	scanf("%d", &n);
	for (int i = 1, a, b; i < n; i++) {
		scanf("%d%d", &a, &b);
		edge[a].push_back(b);
		edge[b].push_back(a);
	}
	// 假设1为根元素,开始建边 
	build(1, 0);
	// 计算u点和v点互连后到各个点的距离 
	scanf("%d %d", &U, &V);
	bfs();
	// 开始查询
	scanf("%d", &Q); 
	for (int i = 0, x, y; i < Q; i++) {
		scanf("%d %d", &x, &y);
		int dis1 = dis[x] + dis[y] - 2, lca;
		if (depth[x] > depth[y]) {
			for (int j = 0; j < father[y].size(); j++) {
				if (father[y][j] == father[x][j+depth[x]-depth[y]]) {
					lca = father[y][j];
					break;
				}
			}
		} else {
			for (int j = 0; j < father[x].size(); j++) {
				if (father[x][j] == father[y][j+depth[y]-depth[x]]) {
					lca = father[x][j];
					break;
				}
			}
		}
		printf("%d\n", min(dis1, depth[x]+depth[y]-2*depth[lca]));
	}
	return 0;
}

G. 小A和小B
其实这道题在月赛时已经写的差不多了,通过了80%的测试点,然而我现在还是没有找到错误。
先记录一下错误代码,是先让A走完所有的可能再让B走,直到B走的步数大于等于A,此时B走的步数就是最终结果:

#include 
#include 
#include 

using namespace std;

const int MAXN = 1005;

const int dr[]={0,0,1,-1,1,-1,1,-1};
const int dc[]={-1,1,0,0,1,-1,-1,1};

int N, M;
int d1[MAXN][MAXN], d2[MAXN][MAXN];
char mi[MAXN][MAXN];
bool vis[MAXN][MAXN];

bool inside (int i, int j) {
	return i >= 0 && j >= 0 && i < N && j < M;
}

int main () {
	int endR, endC;
	scanf("%d %d", &N, &M);
	queue qr, qc;
	for (int i = 0; i < N; i++) {
		for (int j = 0; j < M; j++) {
			getchar();
			scanf("%c", &mi[i][j]);
			if (mi[i][j] == 'C') {
				qr.push(i);
				qc.push(j);
				vis[i][j] = true;
			} else if (mi[i][j] == 'D') {
				endR = i;
				endC = j;
			}
		}
	}
	while (!qr.empty()) {
		int r = qr.front(), c = qc.front();
		qr.pop(); qc.pop();
		for (int i = 0; i < 8; i++) {
			int nr = r + dr[i], nc = c + dc[i];
			if (inside(nr, nc) && !vis[nr][nc] && mi[nr][nc] != '#') {
				qr.push(nr); qc.push(nc);
				vis[nr][nc] = true;
				d1[nr][nc] = d1[r][c] + 1;
			}
		}
	}
	if (!vis[endR][endC]) {
		printf("NO\n");
	} else {
		bool ok = false;
		memset(vis, 0, sizeof(vis));
		vis[endR][endC] = true;
		qr.push(endR); qc.push(endC);
		d2[endR][endC] = 1;
		while (!qr.empty() && !ok) {
			int r = qr.front(), c = qc.front();
			qr.pop(); qc.pop();
			for (int i = 0; i < 4; i++) {
				int nr = r + dr[i], nc = c + dc[i];
				if (inside(nr, nc) && !vis[nr][nc] && mi[nr][nc] != '#') {
					qr.push(nr); qc.push(nc);
					vis[nr][nc] = true;
					d2[nr][nc] = d2[r][c] + 1;
					if (d1[nr][nc] <= d2[nr][nc]/2) {
						printf("YES\n%d", d2[nr][nc]/2);
						ok = true;
						break;
					}
				}
			}
		}
	}
	return 0;
}

以下这份是用双向bfs写的AC代码:

#include 
#include 

using namespace std;

const int MAXN = 1005;

const int dr[]={0,0,1,-1,1,-1,1,-1};
const int dc[]={-1,1,0,0,1,-1,-1,1};

int N, M;
bool vis[2][MAXN][MAXN];
char mi[MAXN][MAXN];
queue qr[2], qc[2];

bool inside (int i, int j) {
	return i >= 0 && j >= 0 && i < N && j < M;
}

void read () {
	scanf("%d %d", &N, &M);
	for (int i = 0; i < N; i++) {
		for (int j = 0; j < M; j++) {
			getchar();
			scanf("%c", &mi[i][j]);
			if (mi[i][j] == 'C') {
				qr[0].push(i);
				qc[0].push(j);
				vis[0][i][j] = true;
			} else if (mi[i][j] == 'D') {
				qr[1].push(i);
				qc[1].push(j);
				vis[1][i][j] = true;
			}
		}
	}
}

bool bfs (int dir, int len) {
	int size = qr[dir].size();
	while (size--) {
		int r = qr[dir].front(), c = qc[dir].front();
		qr[dir].pop(); qc[dir].pop();
		for (int i = 0; i < len; i++) {
			int nr = r + dr[i], nc = c + dc[i];
			if (inside(nr, nc) && !vis[dir][nr][nc] && mi[nr][nc] != '#') {
				if (vis[1-dir][nr][nc]) return true;
				qr[dir].push(nr); qc[dir].push(nc);
				vis[dir][nr][nc] = true;
			}
		}
	}
	return false;
}

int main () {
	read();
	bool ok = false;
	for (int i = 1; !qr[0].empty() || qr[1].empty(); i++) {
		if (bfs(0, 8) || bfs(1, 4) || bfs(1, 4)) {
			ok = true;
			printf("YES\n%d", i);
			break;
		} 
	}
	if (!ok) printf("NO\n");
	return 0;
}

I. 小A取石子
我第一直觉感觉像是博弈论,然后就直接放弃了,因为根本不会。今天补了一下,在网上看到这叫做nim游戏对于nim游戏先手(也就是小A) 赢的情况是当且仅当a1 ^ a2 ^ … ^ an != 0。
至于为什么是这样:

  1. 当必胜态时(XOR不为零时) , 总能取走XOR后的值颗石子,使其进入必败态
  2. 当必败态时(XOR为零时), 取走任意颗石头XOR一定不再为零,所以必败态一定会转移到必胜态

只要懂了这个的话,这道题就非常简单了,如果a1 ^ a2 ^ … ^ an != 0或者可以拿走石子的话A就赢了。
AC代码:

#include 

using namespace std;

const int MAXN = 100010;
int N, K;

int main () {
	scanf("%d%d", &N, &K);
	int a = 0, b;
	bool ok = false;
	for (int i = 1; i <= N; i++) {
		scanf("%d", &b);
		a = a ^ b;
		if (b >= K && K != 0) ok = true; // 可以拿走石头 
	}
	if (a != 0 || ok) printf("YES");
	else printf("NO");
	return 0;
}

J. 小A的数学题
应该是数论里的内容,还不知道是什么知识,暂时先留在这里,等过一段时间再来补。

好了,以上就是所有的题目了。最后的最后,来个总结:在这次小白月赛中,我学了:

  • 单调栈 [H题]
  • 快速幂,快速矩阵 [E题]
  • 将题目中不同的情况的分解 [F题]
  • 双向bfs [G题]
  • 单调栈 [H题]
  • Nim游戏 [I题]
    当然,这一些都只是简单的入了一下门,还需要更多的研究

你可能感兴趣的:(补题记录)