北师大新生赛2014 题解

本文精选了算法、编程、IT技术领域的多个主题,包括但不限于大数据、AI、区块链、测试、文档协作、数据挖掘等,提供了深入的技术解读和实战案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

比赛地址

北师大新生赛2014


A. 无聊的游戏

题意:

A、B两人玩游戏,从集合E={1,2,...,n}中随机取k个数。若这k个数的和是偶数,则A获胜;否则B获胜。请你判断游戏对谁(A、B)有利,或为公平(F)。

k <= n <= 10 ^ 9。

题解:(直接抄的官方题解,自己现场做是打表的)

1)若n为偶数,k为奇数。设n=2*m,则可将1,2,……,n分为(1,2),(3,4),……,(n-1,n)共m对数。因为k为奇数,所以选出的k个数中至少存在一个数a,与它同一对的数b不在这k个数中。将a换成b,则k个数的和的奇偶性改变。于是找到了和为奇数的k个数到和为偶数的k个数的一一对应。也就是说,从1,2,……,n中随机选k个数,和为奇数与和为偶数的可能性是相等的。此时游戏公平。

2)若n为偶数,k为偶数。设k=2*p,此时,存在一些情况,对于k个数中的每个数,与它成对的数都在这k个数中,此时这k个数中共有p个奇数,若p为偶数,则对A有利,若p为奇数,则对B有利。

3)若n为奇数,k为奇数。设k=2*p+1,此时可分为两种情况,一是取到n,一是没有取到n。若取到n,则相当于在1~n-1中选2*p个数,由2)可知若p为偶数,则对B有利,若p为奇数,则对A有利。若没有取到n,则相当于在1~n-1中选2*p+1个数,由1)可知,此时对双方是公平的。因而综合起来,若p为偶数,则对B有利,若p为奇数,则对A有利。

4)若n为奇数,k为偶数。设k=2*p,此时依然分为两种情况,取到n和没有取到n。若取到n,则相当于在1~n-1中选2*p-1个数,由1)可知此时游戏公平。若没有取到n,则相当于在1~n-1中选2*p个数,由2)可知,若p为偶数,则对A有利,若p为奇数,则对B有利。综合起来,若p为偶数,则对A有利,若p为奇数,则对B有利。

代码:

#include <cstdio>
int t, n, k;
int main()
{
	scanf("%d", &t);
	while(t--)
	{
		scanf("%d%d", &n, &k);
		if(n % 2 == 0 && k % 2 == 1)
			puts("F");
		else if(n % 2 == 0 && (k >> 1) % 2 == 0 || n % 2 == 1 && (k - 1 >> 1) % 2 == 1)
			puts("A");
		else
			puts("B");
	}
	return 0;
}

B. Monty Hall problem

题意:

一共有n扇关闭了的门。只有一扇门后是汽车,其他n-1扇门后是山羊。参赛者选定一扇门后,知道门后情形的节目主持人会开启剩下n-1扇门的其中n-2扇,露出n-2只山羊。主持人其后会问参赛者要不要换另一扇仍然关上的门。求参赛者换门之后获得汽车的概率。

3 <= n <= 10 ^ 18。

题解:

参赛者第一次选到正确的门的概率为1 / n,换门之后获奖的概率为(n - 1) / n。

代码:

#include <cstdio>
#include <algorithm>
using namespace std;
int t;
long long n;
int main()
{
	scanf("%d", &t);
	while(t--)
	{
		scanf("%lld", &n);
		printf("%lld/%lld\n", n - 1, n);
	}
	return 0;
}

C. Araleii & Bill的冠名权争夺战 again

题意:

有n个石子,标号1~n,正常来说石子标号大的可以战胜石子标号小的,但有m对反常关系使得石子标号小的可以战胜石子标号大的。

A先选一个石子,若B不能选出另一个石子战胜A的石子,则A获胜,否则游戏继续。

A再选另一个不同于前两个的石子,若可以战胜B的石子,则A获胜,否则B获胜。

1 <= N <= 10 ^ 5, 0 <= M <= min{10 ^ 6, N * (N - 1) / 2}。

题解:

考虑第一种情况,A直接获胜当且仅当存在一个不可战胜的石子。

进入第二种情况,说明任意石子都是可以战胜的,所以A还是赢。

代码:

#include <cstdio>
int t, n, m;
int main()
{
	scanf("%d", &t);
	while(t--)
	{
		scanf("%d%d", &n, &m);
		while(m--)
			scanf("%*d%*d");
		puts("Bill will lose HAHA");
	}
	return 0;
}

D. 柯南的精灵

题意:

精灵的一生分为两个阶段,分别是发育期和更年期。

每天正午,以下两件事情同时发生:

每1个发育期的精灵都会在一瞬间发育起来,变为x个更年期的精灵。

每1个更年期的精灵会分裂成为y个发育期和z个更年期的精灵。

第1天清晨,小兰的家里还没有精灵,每天晚上都会有(p * 当前天数)个发育期的精灵从野外被柯南勾引到小兰家里。

柯南希望知道第n天的清晨小兰家里一共会有多少个精灵。

0 <= x, y, z, p <= 10 ^ 4, 1 <= n <= 10 ^ 9, 保证答案 < 2 ^ 63。

题解:

可以设计一个递推矩阵来做这道题,可以发现需要记录的有:发育期精灵个数f_i,更年期精灵个数g_i,当前天数i,辅助计算的常量1。

递推式为

直接快速幂在时间复杂度O(k^3logn)内可以解决,其中k = 4。

当然这个一定是可以优化成线性递推的,也可以快速幂。至于能不能写出通项公式就不清楚了,以后再说。

代码:

#include <cstdio>
struct Mat
{
	int r, c;
	long long num[4][4];
	Mat operator * (const Mat &x) const
	{
		Mat tmp = {};
		tmp.r = r;
		tmp.c = x.c;
		for(int i = 0; i < r; ++i)
			for(int j = 0; j < c; ++j)
				for(int k = 0; k < x.c; ++k)
					tmp.num[i][j] += num[i][k] * x.num[k][j];
		return tmp;
	}
	Mat pow(int k)
	{
		Mat ret = {}, tmp = *this;
		ret.r = ret.c = r;
		for(int i = 0; i < r; ++i)
			ret.num[i][i] = 1;
		while(k)
		{
			if(k & 1)
				ret = ret * tmp;
			tmp = tmp * tmp;
			k >>= 1;
		}
		return ret;
	}
} A, B, C;
int main()
{
	int t, x, y, z, p, n;
	scanf("%d", &t);
	while(t--)
	{
		scanf("%d%d%d%d%d", &x, &y, &z, &p, &n);
		A.r = 1, A.c = 4, A.num[0][0] = A.num[0][1] = A.num[0][2] = 0, A.num[0][3] = 1;
		B.r = 4, B.c = 4;
		B.num[0][1] = x, B.num[1][0] = y, B.num[1][1] = z, B.num[2][0] = p, B.num[2][2] = B.num[3][2] = B.num[3][3] = 1;
		C = A * B.pow(n);
		printf(">>%lld\n", C.num[0][0] + C.num[0][1]);
	}
	return 0;
}

E. MLX的疯狂睡眠

题意:

一天内有n个睡眠区间[Si, Ti],每一个睡眠区间分别为距离当天0点的第Si秒开始,第Ti秒结束。对于每一次睡眠,MLX都可以参与或者不参与,如果选择了,那么MLX就必须将本次睡眠进行到底。此外,参与睡眠的时间不能重复(即使是刚开始的瞬间和结束的瞬间的重复也是不允许的),请问MLX最多能参与多少次睡眠?

n <= 10 ^ 5。

题解:

这是经典问题,选择最多的不相交区间,可以使用贪心算法,也可使用dp来做,时间复杂度均可以做到O(nlogn)。

先说说无脑的dp方法。按区间右端点排序后重新从左到右标号可以使我们的问题简化(或者按照区间左端点排序后从右到左标号也行),这样是为了定义阶段、确定枚举顺序,标号大的阶段由标号小的阶段转移而来。f[i]表示选择标号等于i的区间时最多可以选取它和它左边的区间个数,则f[i] = max{f[j] + 1},其中第j个区间在第i个区间左边,直接做时间复杂度O(n^2),如果定义g[i]表示选择标号小于等于i的区间时最多可以选取的区间个数,则g[i] = max(g[i - 1], f[i]),可以发现g数组单调递增,那么也不需要f数组了,直接使用g数组即可,g[i] = max{g[i - 1], g[j] + 1},其中第j个区间恰好是第i个区间左边的标号最大的区间,这个j直接二分查找就可以了,时间复杂度做到O(nlogn)。

再说说奇妙的贪心方法。先明确一种情况,如果存在区间包含的关系,选小区间不会使解变差。那么去除包含情况后,如果按照区间右端点排非降序,左端点也是非降序的,否则就会出现包含情况。接下来考虑最左边的一条线段,它的左边没有线段,选它不会使解变差,所以选它,然后再去考虑右边的第一个没有被覆盖的线段,依此类推,时间复杂度O(nlogn)。

代码:

考场写的dp,lower_bound是C++ STL函数。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
struct Line
{
	int l, r;
	bool operator < (const Line &x) const { return r < x.r; }
} line[233333];
int t, n;
int r[233333], f[233333];
int main()
{
	scanf("%d", &t);
	while(t--)
	{
		memset(f, 0, sizeof f);
		memset(r, 0, sizeof r);
		scanf("%d", &n);
		for(int i = 1; i <= n; ++i)
			scanf("%d%d", &line[i].l, &line[i].r);
		sort(line + 1, line + n + 1);
		for(int i = 1; i <= n; ++i)
		{
			r[i] = line[i].r;
			f[i] = max(f[i - 1], f[upper_bound(r + 1, r + i, line[i].l - 1) - r - 1] + 1);
		}
		printf("%d\n", f[n]);
	}
	return 0;
}

F. Star Trek: First Contact

题意:

给定n个一次性技能,每个技能需要消耗魔法a_i,攻击力为b_i,求消耗魔法不超过A的情况下是否能造成B点伤害。

n <= 100, A, B, a_i, b_i <= 1000。

题解:

经典背包dp,f[i][j]表示前i个技能消耗j点魔法做出的最大伤害,则f[i][j] = max{f[i - 1][j], f[i - 1][j - a[i]] + b[i]},第一维可以滚动掉,答案即是否存在f[n][j] >= B。

时间复杂度O(nA)。

代码:

#include <cstdio>
#include <cstring>
int t, n, aa, bb, a[101], b[101], f[1001];
int main()
{
	int Case = 0;
	scanf("%d", &t);
	while(t--)
	{
		memset(f, 0, sizeof f);
		scanf("%d%d%d", &aa, &bb, &n);
		for(int i = 1; i <= n; ++i)
			scanf("%d", a + i);
		for(int i = 1; i <= n; ++i)
			scanf("%d", b + i);
		for(int i = 1; i <= n; ++i)
			for(int j = aa; j >= a[i]; --j)
				if(f[j] < f[j - a[i]] + b[i])
					f[j] = f[j - a[i]] + b[i];
		bool flag = 0;
		for(int i = 0; i <= aa; ++i)
			if(f[i] >= bb)
			{
				flag = 1;
				break;
			}
		printf("Case #%d: %s\n", ++Case, flag ? "YES" : "NO");
	}
	return 0;
}

G. 平面切割者

题意:

有两个同心圆,平面上有一大一小两个同心圆,有n条大圆的弦,这些弦在大圆内两两相交,各弦均与小圆相交于两点,且不存在三弦共点,也不存在两弦和小圆交于一点的情况。问大圆内的平面被切割成多少个区域。t组询问。

0 <= t, n <= 20000。

题解:

小圆内切割出的平面增加第n条弦会多n个平面,总共1 + 1 + 2 + ... + n = n * (n + 1) / 2 + 1个平面。

圆环内切割出的平面增加第n条弦会多2个平面,总共2n个平面。

注意n = 0时共2个平面。

代码:

#include <cstdio>
int n, t;
int main()
{
	scanf("%d", &t);
	while(t--)
	{
		scanf("%d", &n);
		if(!n)
			puts("2");
		else
			printf("%d\n", n * (n + 1) / 2 + 1 + n * 2);
	}
	return 0;
}

H. 顽皮的字母

题意:

字母a~z标号1~26,标号为2k-1的字母和标号为2k的字母会相消,给定一个长度为n的字符串,问相消后的结果,每次相消后字符串会合并以备下次相消。

题解:

可以利用一个栈来从左到右模拟相消情况,由于每个元素最多进栈一次、出栈一次,时间复杂度O(n)。

代码:

#include <cstdio>
#include <cstring>
int t, top;
char str[233333], sta[233333];
int main()
{
	scanf("%d", &t);
	while(t--)
	{
		scanf("%s", str);
		int len = strlen(str);
		top = -1;
		for(int i = 0; i < len; ++i)
		{
			sta[++top] = str[i];
			while(top > 0 && sta[top] != sta[top - 1] && (sta[top] - 'a' >> 1) == (sta[top - 1] - 'a' >> 1))
				top -= 2;
		}
		if(top < 0)
			puts("sad!");
		else
		{
			sta[++top] = '\0';
			puts(sta);
		}
	}
	return 0;
}

小记

做题顺序BDHAGE,DH是FB。先做了D好像带歪了borad……前一个半小时rank1带着borad各种乱歪,差不多一个小时后就开始“玩泥巴”了,当时C的结论没想出来,在用各种奇怪的方法建图,F则是忘了这种dp技巧,还是因为做题不够多,思想有些固化。本次比赛整体难度偏低,适合锻炼思路,个人做题想题写题均有待加强。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值