1010 - Kong Ming Qi
题目大意
有 n ∗ m n*m n∗m 颗棋子,放置于 ( 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 4∗4 以内然后暴力
但是暴力太麻烦了不是很想写
通过模拟可以发现 n = 2 n=2 n=2 并且 m = 2 m=2 m=2 肯定能变成一颗
n ≥ 3 n\ge3 n≥3 并且 m ≥ 3 m\ge3 m≥3 的时候可以通过上述操作变到剩下 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, m≥3 和
m
=
2
,
n
≥
3
m=2,\space n\ge3
m=2, n≥3 的情况,我们有以下操作:
同样引用了官方题解的
发现同样可以通过操作变到剩下 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(n−1), fi=(fi=1+2(n−1)(n−2)∗(n(n−2))i−2)∗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=fi−1∗n2+2(n−1)(n−2)i−1ni
分母为 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=(1−pi−1)∗n22(n−1)+pi−1∗n2n2−2
( 1 − p i − 1 ) ∗ 2 ( n − 1 ) n 2 (1-p_{i-1})*\frac{2(n-1)}{n^2} (1−pi−1)∗n22(n−1) 是从无贡献变为有贡献的概率, p i − 1 ∗ n 2 − 2 n 2 p_{i-1}*\frac{n^2-2}{n^2} pi−1∗n2n2−2 是有贡献继续产生贡献的概率
化简后可以得到 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(n−1)+pi−1∗nn−2
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) n2∗pi−n∗(n−1)=n∗(n−2)∗pi−1−(n−1)∗(n−2)
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∗(n∗pi−(n−1))=(n−2)∗(n∗pi−1−(n−1))
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 n∗pm−(n−1)=(nn−2)m∗(n∗p0−(n−1))=−(n−1)∗(nn−2)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) n∗pm=(n−1)∗(1−(nn−2)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;
}