23.7.20 杭电暑期多校2部分题解

1001 - Alice Game

题目大意

有一个长度为 n n n 的怪物序列( n n n 可能为 0 0 0),给定一个 k k k,轮到某人回合时会有两种操作:

  1. 选择一个连续的序列,它的长度小于等于 k k k,将其全部删除

  2. 选择一个连续的序列,将其中连续的 k k k 个数删除,使删除后原序列恰好变成两个非空序列

Alice和Bob轮流操作,问谁会赢

解题思路

先进行小数据推导:

  1. 1 ≤ n ≤ k 1\le n\le k 1nk 时,一次操作直接搞定,Alice赢

  2. n = 0 n=0 n=0 或者 n = k + 1 n=k+1 n=k+1 时,先手无法操作,Bob赢

  3. 2 k + 2 ≤ n ≤ 3 k + 1 2k+2\le n\le 3k+1 2k+2n3k+1 时,先手可以自己决定是否在此区间内获胜

若剩下的区间存在先手必败状态便会改变总游戏的先手胜负状态

因此Alice为了赢会尽量不把2和3的情况留给Bob

n = 5 k + 3 n=5k+3 n=5k+3 时,Alice无论如何操作总会留下让Bob可以改变总游戏先手胜负状态的区间,因此必败

以此类推,可以发现当 n % ( 4 k + 2 ) = k + 1 n\%(4k+2)=k+1 n%(4k+2)=k+1 时Bob获

其余状态就是Alice获胜

code

#include <bits/stdc++.h>
using namespace std;
int t, k, n;
int main() {
    scanf("%d", &t);
    while (t --) {
        scanf("%d%d", &k, &n);
        if (n % (4 * k + 2) == k + 1 || n == 0) printf("Bob\n");
        else printf("Alice\n");
    }
    return 0;
}

1011 - SPY finding NPY

题目大意

1 1 1 n n n 随机排序,你需要确定一个下标 k k k 进行以下操作( 0 ≤ k < n 0\le k<n 0k<n):

  1. 取前 k k k 个数中的最大数记为 x x x,如果 k = 0 k=0 k=0,那么 x = − 1 x=-1 x=1

  2. k + 1 k+1 k+1 n − 1 n-1 n1 中去枚举,找到第一个大于 x x x 的数

  3. 如果第二步没有找到这个数,取第 n n n 个数

问你如何取 k k k 才能使最后取的数为 n n n 的概率最大

解题思路

拿到这道题第一感觉是道数学题,所以先推公式

所求概率等于合法方案数除以总方案数,所以我们只要推合法方案数即可

n n n 一定的情况下对于每个 k k k k = 0 k=0 k=0 时方案数为 ( n − 1 ) ! (n-1)! (n1)!

k ≠ 0 k\ne0 k=0 时,假设 i i i n n n 这个数的位置, j j j 是前 k k k 个数中最大的数

如果暴力求解,可以计算出合法方案数为 k ∑ i = k + 1 n ∑ j = i − 1 n − 1 A j − 1 i − 2 A n − i n − i k\sum_{i=k+1}^n\sum_{j=i-1}^{n-1}A_{j-1}^{i-2}A_{n-i}^{n-i} ki=k+1nj=i1n1Aj1i2Anini

解释: j j j 可以放在 k k k 个位置,因此有 k k k 个方案

再用 1 1 1 j − 1 j-1 j1 去填补前 i i i 位,空位有 i − 2 i-2 i2 个,因此有 A j − 1 i − 2 A_{j-1}^{i-2} Aj1i2 个方案

最后还剩下 n − i n-i ni 个位置要用剩下 n − i n-i ni 个数去填,因此有 A n − i n − i A_{n-i}^{n-i} Anini 个方案

然后我就卡住了,因为我不会化简,所以就去打一下 n = 4 n=4 n=4 的情况

打表结果如下:
在这里插入图片描述
用自己的式子计算如下:
在这里插入图片描述
然后发现好像和自然对数有关系,找下规律,猜想 k ∑ i = k + 1 n ∑ j = i − 1 n − 1 A j − 1 i − 2 A n − i n − i = k ∑ i = k n − 1 ( n − 1 ) ! i k\sum_{i=k+1}^n\sum_{j=i-1}^{n-1}A_{j-1}^{i-2}A_{n-i}^{n-i}=k\sum_{i=k}^{n-1}\frac{(n-1)!}{i} ki=k+1nj=i1n1Aj1i2Anini=ki=kn1i(n1)!

在考场上直接跳过验证进入了下一步,赛后尝试推了一下还是推不出来,然后尝试换了个思路

在确定了 n n n 所在的位置在 i i i 之后,其实只需要保证前 i − 1 i-1 i1 个数中的最大值在 1 1 1 k k k 就可以了

而这种情况在全部的排列中出现的概率为 k i − 1 \frac{k}{i-1} i1k

那么合法方案数就是 ∑ i = k + 1 n A n − 1 n − 1 ∗ k i − 1 = k ∑ i = k n − 1 ( n − 1 ) ! i \sum_{i=k+1}^{n}A_{n-1}^{n-1}*\frac{k}{i-1}=k\sum_{i=k}^{n-1}\frac{(n-1)!}{i} i=k+1nAn1n1i1k=ki=kn1i(n1)!

不难靠直觉发现这是个关于 k k k 先増后减的函数

可以三分,可以靠样例和公式判断答案在 n e \frac{n}{e} en 左右去找,甚至可以暴力

因为暴力不会超时且更简单,所以我选择了暴力

比较的时候可以把 ( n − 1 ) ! (n-1)! (n1)! 约掉,只要比较 k ∑ i = k n − 1 1 i k\sum_{i=k}^{n-1}\frac{1}{i} ki=kn1i1 就可以了

code

#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 9;
const double EPS = 1e-12;
int t, n, ans;
double f[N], num;
int main() {
    for (int i = 1; i < N; ++ i) f[i] = f[i - 1] + (1.0 / i);
    //预处理,f[i]为数列{1/n}的前i项和,要得到表达式只需要差分一下就可以了
    scanf("%d", &t);
    while (t --) {
        scanf("%d", &n);
        ans = 0, num = 1.0;//num初始值为k=0时的方案数除以(n-1)!
        for (int i = 1; i < n; ++ i)
            if ((f[n - 1] - f[i - 1]) * i - num <= EPS) {ans = i - 1; break;}
            //找到第一个下降的位置并记录前一位即是所求最大值的位置,浅浅注意一下double比大小的精度
            else num = (f[n - 1] - f[i - 1]) * i;
        printf("%d\n", ans);
    }
    return 0;
}

1010 - Klee likes making friends

题目大意

n n n 个人,要和第 i i i 个人交朋友会花费 a i a_i ai,每连续 m m m 个人中需要选择至少两人交朋友,问最少花费多少

解题思路

考虑dp,设 f i , j f_{i,j} fi,j 表示最后一个人为 i i i,倒数第二个人为 j j j 的最少花费

显然需要保证 i − j < m i-j<m ij<m,并且到数第三个人应该在 k ∈ [ i − m ,   j − 1 ] k\in[i-m,\space j-1] k[im, j1],处理一个后缀最小值

因为转移过程中 i ,   j ,   k i,\space j,\space k i, j, k 差不超过 m m m,对于数组开不下的情况可以用取模(滚动数组)解决

f i , j f_{i,j} fi,j 就改成表示最后一个人为 i   m o d   m i\space mod\space m i mod m,倒数第二个距离最后一个为 j j j 的最小花费

转移比较容易想就不赘述了

code

#include <bits/stdc++.h>
using namespace std;
const int N = 2e4 + 9;
const int M = 2009;
int t, n, m, a[N], f[M][M], b[M][M], c[M], g[M] = {0x3f3f3f3f}, sum;
int main() {
	scanf("%d", &t);
	while (t --) {
		scanf("%d%d", &n, &m);
		for (int i = 0; i < n; ++ i) scanf("%d", &a[i]);
		sum = 0x3f3f3f3f;
		for (int i = 0; i < m; ++ i) {
			f[i][0] = 0x3f3f3f3f;
			for (int j = 1; j <= m; ++ j) {
				if (i - j < 0) b[i][j] = 0x3f3f3f3f;
				else b[i][j] = a[i] + a[i - j];
				f[i][j] = min (f[i][j - 1], b[i][j]);
			}
		}
		for (int i = m ; i < n; ++ i) {
			for (int j = 1; j <= m; ++ j)
				c[j] = a[i] + f[(i - j) % m][m - j],
				g[j] = min(g[j - 1], c[j]);
			for (int j = 1; j <= m; ++ j)
				b[i % m][j] = c[j], f[i % m][j] = g[j];
		}
		for (int i = 1; i <= m; ++ i)
			sum = min(sum, f[(n - i) % m][m - i]);
		printf("%d\n", sum);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值