23.7.27 杭电暑期多校4部分题解

1010 - Kong Ming Qi

题目大意

n ∗ m n*m nm 颗棋子,放置于 ( n + 2 ) ∗ ( m + 2 ) (n+2)*(m+2) (n+2)(m+2) 的棋盘的中心位置,用孔明棋的走法问最后最少剩下多少颗棋子

解题思路

发现 n = 1 n=1 n=1 m = 1 m=1 m=1 的情况只能减少一半的棋子

其余情况下进行一个模拟,发现如图操作能在不改变另外棋子的情况下减少三颗棋子:
在这里插入图片描述
不太好画就直接引用官方题解的了

因为这种操作还能将边大于等于 5 5 5 的情况每次减少 3 3 3 化简到 4 ∗ 4 4*4 44 以内然后暴力

但是暴力太麻烦了不是很想写

通过模拟可以发现 n = 2 n=2 n=2 并且 m = 2 m=2 m=2 肯定能变成一颗

n ≥ 3 n\ge3 n3 并且 m ≥ 3 m\ge3 m3 的时候可以通过上述操作变到剩下 1 ,   2 ,   3 1,\space2,\space3 1, 2, 3 颗棋子,前两者最后答案为 1 1 1,最后一个为 2 2 2

再来看 n = 2 ,   m ≥ 3 n=2,\space m\ge3 n=2, m3 m = 2 ,   n ≥ 3 m=2,\space n\ge3 m=2, n3 的情况,我们有以下操作:
在这里插入图片描述
同样引用了官方题解的

发现同样可以通过操作变到剩下 1 ,   2 ,   3 1,\space2,\space3 1, 2, 3 颗棋子,前两者最后答案为 1 1 1,最后一个为 2 2 2

由此,对于 n ≠ 1 n\ne1 n=1 并且 m ≠ 1 m\ne1 m=1 的情况,只要去看 n n n m m m 中是否有 3 3 3 的倍数

有的话最后一定会剩下 3 3 3 颗棋子,答案为 2 2 2,其余为 1 1 1

code

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

1005 - Data Generation

题目大意

有一个 1 1 1 n n n 的顺序排列,交换 n n n 次,问期望有多少个数不在原来的位置上

解题思路

第一感觉是一个打表找规律的题,那么先打表再说

#include <bits/stdc++.h>
using namespace std;
const int N = 109;
int t, n, m, a[N];
long long cnt, tmp;
long long pw(long long a, int b) {
	long long res = 1;
	while (b) {
		if (b & 1) res = res * a;
		a = a * a;
		b >>= 1;
	}
	return res;
}
void dfs(int x) {
    if (x == m) {
        for (int i = 1; i <= n; ++ i)
            if (a[i] != i) ++ cnt;
        ++ tmp;
        return;
    }
    for (int i = 1; i <= n; ++ i)
        for (int j = 1; j <= n; ++ j)
            swap(a[i], a[j]), dfs(x + 1), swap(a[i], a[j]);
}
int main() {
    for (int i = 1; i <= 100; ++ i)
        a[i] = i;
    for (int i = 1; i <= 6; ++ i) {
    	for (int j = 1; j <= 5; ++ j) {
    		n = i, m = j;
    		cnt = tmp = 0;
    		dfs(0);
        	printf("(%d,%d):%10lld/%10lld ", n, m, cnt, tmp);
        }
        printf("\n");
    }    
    return 0;
}

在这里插入图片描述
打完之后就硬推式子找规律

对于同一行的分子 f i f_i fi f 1 = 2 n ( n − 1 ) ,   f i = ( f i = 1 + 2 ( n − 1 ) ( n − 2 ) ∗ ( n ( n − 2 ) ) i − 2 ) ∗ n 2 f_1=2n(n-1),\space f_i=(f_{i=1}+2(n-1)(n-2)*(n(n-2))^{i-2})*n^2 f1=2n(n1), fi=(fi=1+2(n1)(n2)(n(n2))i2)n2

化简后 f i = f i − 1 ∗ n 2 + 2 ( n − 1 ) ( n − 2 ) i − 1 n i f_i=f_{i-1}*n^2+2(n-1)(n-2)^{i-1}n^i fi=fi1n2+2(n1)(n2)i1ni

分母为 n 2 m n^{2m} n2m

可以用矩阵快速幂来处理,但是不太会,通项公式也推不出来,正赛上就止步于此了

赛后换了个思路考虑

假设某个数在第 i i i 次有贡献的概率为 p i p_i pi,那么 p 0 = 0 ,   p i = ( 1 − p i − 1 ) ∗ 2 ( n − 1 ) n 2 + p i − 1 ∗ n 2 − 2 n 2 p_0=0,\space p_i=(1-p_{i-1})*\frac{2(n-1)}{n^2}+p_{i-1}*\frac{n^2-2}{n^2} p0=0, pi=(1pi1)n22(n1)+pi1n2n22

( 1 − p i − 1 ) ∗ 2 ( n − 1 ) n 2 (1-p_{i-1})*\frac{2(n-1)}{n^2} (1pi1)n22(n1) 是从无贡献变为有贡献的概率, p i − 1 ∗ n 2 − 2 n 2 p_{i-1}*\frac{n^2-2}{n^2} pi1n2n22 是有贡献继续产生贡献的概率

化简后可以得到 p i = 2 ( n − 1 ) n 2 + p i − 1 ∗ n − 2 n p_i=\frac{2(n-1)}{n^2}+p_{i-1}*\frac{n-2}{n} pi=n22(n1)+pi1nn2

n 2 ∗ p i − n ∗ ( n − 1 ) = n ∗ ( n − 2 ) ∗ p i − 1 − ( n − 1 ) ∗ ( n − 2 ) n^2*p_i-n*(n-1)=n*(n-2)*p_{i-1}-(n-1)*(n-2) n2pin(n1)=n(n2)pi1(n1)(n2)

n ∗ ( n ∗ p i − ( n − 1 ) ) = ( n − 2 ) ∗ ( n ∗ p i − 1 − ( n − 1 ) ) n*(n*p_i-(n-1))=(n-2)*(n*p_{i-1}-(n-1)) n(npi(n1))=(n2)(npi1(n1))

n ∗ p m − ( n − 1 ) = ( n − 2 n ) m ∗ ( n ∗ p 0 − ( n − 1 ) ) = − ( n − 1 ) ∗ ( n − 2 n ) m n*p_m-(n-1)=(\frac{n-2}{n})^m*(n*p_0-(n-1))=-(n-1)*(\frac{n-2}{n})^m npm(n1)=(nn2)m(np0(n1))=(n1)(nn2)m

因为总共有 n n n 个数,所以最后的答案要乘 n n n

所以答案 n ∗ p m = ( n − 1 ) ∗ ( 1 − ( n − 2 n ) m ) n*p_m=(n-1)*(1-(\frac{n-2}{n})^m) npm=(n1)(1(nn2)m)

验证一下边界情况也满足,直接一个公式就行

code

#include <bits/stdc++.h>
using namespace std;
const long long MOD = 998244353;
int t;
long long n, m;
long long sub(long long a, long long b) {return a - b < 0 ? a - b + MOD : a - b;}
long long mul(long long a, long long b) {return a * b % MOD;}
long long pw(long long a, long long b) {
    long long res = 1;
    while (b) {
        if (b & 1) res = mul(res, a);
        a = mul(a, a);
        b >>= 1;
    }
    return res;
}
int main() {
    scanf("%d", &t);
    while (t --) {
        scanf("%lld%lld", &n, &m); n %= MOD;
        printf("%lld\n", mul(sub(n, 1), sub(1, pw(mul(sub(n, 2), pw(n, MOD - 2)), m))));
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值