比赛地址
北师大新生赛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技巧,还是因为做题不够多,思想有些固化。本次比赛整体难度偏低,适合锻炼思路,个人做题想题写题均有待加强。