爆零考试后一场T1wait
题目描述
题目描述
有一n*m的棋盘,每次随机染黑一个位置(可能染到已经黑了的),当某一行或者一列全为黑色时停止,求期望染色次数(mod 998244353)
输入
一行两个正整数n,m
输出
期望结果
数据范围
对于20%的数据n,m<=5
对于100%的数据n,m<=1000
思路
不知道为什么每次考试看到数学期望值总是1分都得不到我们看到这道题,我们可以先确定一点,我们先设答案为ans(不是期望答案),则
a
n
s
=
m
i
n
(
m
i
n
(
a
i
)
(
i
∈
[
1
,
n
]
)
,
m
i
n
(
b
i
)
(
i
∈
[
1
,
m
]
)
)
ans=min(min(a_i)(i\in[1,n]),min(b_i)(i\in[1,m]))
ans=min(min(ai)(i∈[1,n]),min(bi)(i∈[1,m])),其中
a
i
b
i
{a_i}{b_i}
aibi表示第i行,第i列的最小步数,我们这里又有一个公式
a
n
s
=
∑
S
∈
U
(
−
1
)
∣
S
∣
+
1
m
a
x
S
ans=\sum_{S \in U}(-1)^{|S|+1}max{S}
ans=∑S∈U(−1)∣S∣+1maxS,这是基于容斥原理推出来的,至于为什么我之后再补上来吧因为我也不知道然后,我们求ans的数学期望值,我们可以设为
E
(
a
n
s
)
E(ans)
E(ans),我们实际上就是求
∑
S
∈
U
(
−
1
)
∣
S
∣
+
1
E
(
m
a
x
S
)
\sum_{S \in U}(-1)^{|S|+1}E(max{S})
∑S∈U(−1)∣S∣+1E(maxS),U就是所有
a
i
,
b
i
a_i,b_i
ai,bi所组成的一个集合,我们考虑如何求出这个值,我们想一下假设当前取出i行,j列,则我们可以用容斥算出取了多少个,即
i
∗
m
+
j
∗
n
−
i
∗
j
i*m+j*n-i*j
i∗m+j∗n−i∗j,这个肯定没有问题吧?除非是个脑残,我们把它带到
S
S
S里面,就可以知道它的值就为
(
n
i
)
∗
(
m
j
)
∗
∑
i
=
0
i
∗
m
+
j
∗
n
−
i
∗
j
n
∗
m
i
{n \choose i} * {m \choose j} * \sum_{i=0}^{i*m+j*n-i*j} \frac{n*m}i
(in)∗(jm)∗∑i=0i∗m+j∗n−i∗jin∗m,我觉得可能后面那个
∑
\sum
∑可能比较难以理解,实际上我们可以通过观察一下,假设从n行里选1行,m列里选1列我可能行列不分,我们就选了1个数,就是从
n
∗
m
n*m
n∗m中选出一个数,就是
1
n
∗
m
\frac{1}{n*m}
n∗m1,数学期望步数从理论上来说就是
n
∗
m
1
\frac{n*m}{1}
1n∗m次就可以填满,然后选2个数的话也一样,但由于是求数学期望,就是加上一个
n
∗
m
2
\frac{n*m}2
2n∗m了,而不是乘,我们就可以理解到那个
∑
\sum
∑的含义了,然后我们再具体求值的时候,本来理论上应该是
2
x
2^x
2x,其中x由n和m决定,但我们实际上可以通过枚举i和j使我们的时间复杂度由一个指数级别降到
O
(
n
2
)
O(n^2)
O(n2),然后逆元我们可以通过线性递推求出来,然后这道题就这样了,也没有什么好说的了吧
代码
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define Int register int
#define int long long
#define MAXN 1005
#define mod 998244353
int n,m;
int fac[MAXN],f[MAXN * MAXN],inv[MAXN * MAXN],invfac[MAXN * MAXN];
int C(int n,int m)
{
return fac[n] * invfac [m] % mod * invfac [n - m] % mod;
}
void init()
{
f[0] = 1,f[1] = n * m,inv[1] = 1;
for (Int i = 2;i <= n * m;++ i)
{
inv[i] = (int)(mod - mod / i) * inv[mod % i] % mod;
f[i] = (f[i - 1] + n * m * inv [i] % mod) % mod;
}
fac[0] = 1;invfac[0] = 1;
for (Int i = 1;i <= max(n,m);++ i)
{
fac[i] = fac[i - 1] * i % mod;
invfac[i] = invfac[i - 1] * inv[i] % mod;
}
}
void read (int &x)
{
x = 0;char c = getchar();int f = 1;
while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}
while (c >= '0' && c <= '9'){x = (x << 3) + (x << 1) + c - '0';c = getchar();}
x *= f;return ;
}
void write (int x)
{
if (x < 0){x = -x;putchar ('-');}
if (x > 9) write (x / 10);
putchar (x % 10 + '0');
}
signed main()
{
//freopen ("wait.in","r",stdin);
//freopen ("wait.out","w",stdout);
read (n),read (m);
init();
int tot = 0;
for (Int i = 0;i <= n;++ i)
for (Int j = 0;j <= m;++ j)
{
if (!i && !j) continue;
int a = i * m + j * n - i * j;
int now = C (n,i) % mod * C (m,j) % mod * f[a] % mod;
if ((i + j + 1) % 2 == 0) tot = (tot + now) % mod;
else tot = (tot % mod + mod - now % mod) % mod;
}
write ((tot % mod + mod) % mod),putchar ('\n');
return 0;
}
爆零考试后一场T2game
题目描述
题目描述:
双方进行游戏,有两个正整数a,b
若a<=b
双方轮流进行选择下列两种操作之一
1)将b对a取模,即b变为b%a
2)从b中减去a的幂,不能减成负数,即b变为b-ak(b-ak>=0且k为正整数)
若a>b,类似
若a,b中之一为0,则无法进行,无法进行者败
输入初始a,b,判断先后手谁有必胜策略
思路
我们先来想一下30分的做法,我们可以用一个递归程序,去判断对于每个
(
a
,
b
)
(a,b)
(a,b)操作它的人是否能赢,设f数组为是否可行,则
f
[
i
]
[
j
]
=
m
a
x
(
f
[
i
]
[
j
m
o
d
  
i
]
,
∑
k
=
1
i
k
<
=
j
d
p
[
i
]
[
j
−
i
k
]
)
f[i][j]=max(f[i][j\mod\ i],\sum_{k=1}^{i^k<=j}dp[i][j-i^k])
f[i][j]=max(f[i][jmod i],∑k=1ik<=jdp[i][j−ik]),实际上我们这个东西用递归实现之后我们加上记忆化递归,就可以实现
O
(
n
)
O(n)
O(n)的算法时间复杂度,但是对于我们题目中
a
,
b
<
=
1
0
18
a,b<=10^{18}
a,b<=1018还远远不能满足,我们可以设
j
=
j
m
o
d
  
i
+
k
∗
i
j=j\mod i + k*i
j=jmodi+k∗i,这里的k与题目描述中的不是同一个东西,根据WK巨佬所发现的规律,我们发现当k模2余1和k跟i相等的时候
f
[
i
]
[
j
]
f[i][j]
f[i][j]是可行的,为什么呢?大家可以输出一下,找一下规律,就可以发现了。我们还是略微理论证明一下吧,我们考虑a是奇数的时候,我们选了之后给对方,对方再一搞,自己的奇偶性没变,所以不论自己怎么搞,都是对方先嗝屁掉。我们再来证明为什么
d
p
[
i
]
[
j
−
i
k
]
dp[i][j - i^k]
dp[i][j−ik]每
i
+
1
i+1
i+1一个循环,我们考虑不正面刚因为刚不出来用侧面证明——数学归纳法,我们可以先假设在已知的段数都满足,那么我们可以尝试证明
d
p
[
i
]
[
j
−
i
p
]
dp[i][j-i^{p}]
dp[i][j−ip]可以推到
d
p
[
i
]
[
j
−
i
p
−
(
i
+
1
)
]
dp[i][j-i^{p-(i+1)}]
dp[i][j−ip−(i+1)],然后读者们可以尝试自己证明一下因为本蒟蒻实在不会了,然后我们还可以发现当
k
=
i
k=i
k=i的时候
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]也是可行的,但我实在不知道怎么证明了,就你们去证明吧,相信你们!
代码
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define Int register int
#define int long long
bool pd (int a,int b)
{
if (!a) return 0;
if (!pd (b % a,a)) return 1;
int k = ((int)b / a - 1) % (a + 1);
if (k % 2 || k == a) return 1;
else return 0;
}
void read (int &x)
{
x = 0;char c = getchar();int f = 1;
while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}
while (c >= '0' && c <= '9'){x = (x << 3) + (x << 1) + c - '0';c = getchar();}
x *= f;return ;
}
void write (int x)
{
if (x < 0){x = -x;putchar ('-');}
if (x > 9) write (x / 10);
putchar (x % 10 + '0');
}
signed main()
{
//freopen ("game.in","r",stdin);
//freopen ("game.out","w",stdout);
int times;
read (times);
while (times --)
{
int a,b;
read (a),read (b);
if (a > b) swap (a,b);
if (pd (a,b)) puts ("First");
else puts ("Second");
}
return 0;
}
爆零考试后一场T3R
题目描述
题目描述
有两个1~n的排列A,B,序列C一开始为空,每次可以选择进行以下两种操作之一
1)若A不为空,则可取出A的开头元素放在序列C的末尾
2)若B不为空,则可取出B的开头元素放在序列C的末尾
这样当A,B皆为空时,C称为排列A,B的合并,其长度为2*n
记F(A,B)为A,B的所有可能合并的总数
求对于所有可能的1~n的排列A,B,F(A,B)的和,mod 998244353
思路
还是先讲一下20分的做法吧,我们可以打一个全排,然后
O
(
n
!
2
)
O(n!^2)
O(n!2)暴力统计,20分。考虑40分接近40分。我们实际上有一个循环是可以省略的,我们可以固定一个数组,因为都是一样的。考虑满分做法,我们设
d
p
[
i
]
[
j
]
[
k
]
dp[i][j][k]
dp[i][j][k]表示现在A数组到了i位,B数组到了j位,前面一共有k个重复的数出现,这个状态储存的是情况总和,我们可以非常不轻松地列出dp式:
d
p
[
i
]
[
j
]
[
k
]
=
d
p
[
i
−
1
]
[
j
]
[
k
−
1
]
∗
(
j
−
k
+
1
)
+
d
p
[
i
]
[
j
−
1
]
[
k
−
1
]
∗
(
i
−
k
+
1
)
+
d
p
[
i
−
1
]
[
j
]
[
k
]
∗
(
n
−
(
i
+
j
−
k
−
1
)
)
+
d
p
[
i
]
[
j
−
1
]
[
k
]
∗
(
n
−
(
i
+
j
−
k
−
1
)
)
−
∑
i
=
1
k
d
p
[
i
−
d
]
[
j
−
d
]
[
k
−
d
]
∗
(
n
−
(
i
+
j
−
k
−
d
)
d
)
∗
g
(
d
)
∗
d
!
dp[i][j][k]=dp[i-1][j][k-1]*(j-k+1)+dp[i][j-1][k-1]*(i-k+1)+dp[i-1][j][k]*(n-(i+j-k-1))+dp[i][j-1][k]*(n-(i+j-k-1))-\sum_{i=1}^{k}dp[i-d][j-d][k-d]*{{n-(i+j-k-d)} \choose d} * g(d) * d!
dp[i][j][k]=dp[i−1][j][k−1]∗(j−k+1)+dp[i][j−1][k−1]∗(i−k+1)+dp[i−1][j][k]∗(n−(i+j−k−1))+dp[i][j−1][k]∗(n−(i+j−k−1))−∑i=1kdp[i−d][j−d][k−d]∗(dn−(i+j−k−d))∗g(d)∗d!,这个式子表示A数组选了i-1个数,B数组选了j个,有k-1个重复的,你现在从B数组中原本不重复的数中选一个数放到i位,就是
d
p
[
i
−
1
]
[
j
]
[
k
−
1
]
∗
(
j
−
k
+
1
)
dp[i-1][j][k-1]*(j-k+1)
dp[i−1][j][k−1]∗(j−k+1),
j
−
k
+
1
j-k+1
j−k+1是不重复的个数,然后后一个转移也差不多,然后再后面一个转移就是说A数组中i-1个数,B数组中j个,有k个重复的,你就只能选一个已经重复出现的,就是有
n
−
(
i
+
j
−
k
−
1
)
n-(i+j-k-1)
n−(i+j−k−1)个已经重复的,然后再后面一个转移也差不多,然后再后面一个
∑
\sum
∑就是说从k个重复的数中我们选d个出来,为什么呢?因为我们前面在算的时候是把它算重了,它有
d
p
[
i
−
d
]
[
j
−
d
]
[
k
−
d
]
dp[i-d][j-d][k-d]
dp[i−d][j−d][k−d]的原本资本,就是说把i、j、k中的d都不看了,我们知道这d个数无论怎么排,效果都是一样的,所以还要乘上一个
d
!
d!
d!,我们除了这d个,还有
(
i
−
d
)
+
(
j
−
d
)
−
(
k
−
d
)
(i-d)+(j-d)-(k-d)
(i−d)+(j−d)−(k−d)个数互不相同,我们从n中减去它们所组成的集合中中选出d个数,所以这就是那个组合数的含义。那一个关键问题来了,我们怎么算g(x)呢?g(x)又是什么意思呢?g(x)表示的就是现在有一个长度为2*x的串有多少个"好数",那"好数"又是什么呢?我们定义一个数组为好数,当且仅当它每个数出现了两次,且它的任意前缀不能是一个好数,不然的话我们就会算重,然后就需要用容斥原理这种又慢又烦人的算法,知道了它的定义那又怎么算呢?我们实际上可以用卡塔兰数,我们可以想象存在一个二维地图,你要从左下走到右上,不能超过中间的斜线的走法有多少种,我们把选a中的数想象成往右走,把选b中的数想象成往上走,然后不能选一个超过两个,否则就会重复,你会发现这似乎与卡塔兰数的百度中的图很像,实际上我们就可以这么算,但是要注意我们完全不能碰到,所以卡塔兰数的话你的自变量需要减一再带进去。然后这道题我能说的就到这里了,其余如果你有疑问也不在我的能力范围以内,所以请自行理解,如有不便请拨打你妈妈的电话寻求心理安慰,对此我不感任何抱歉
代码
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define Int register int
#define int long long
#define mod 998244353
#define MAXN 105
int n;
int fac[MAXN << 1],invfac[MAXN << 1],dp[MAXN][MAXN][MAXN];
int quike_pow (int a,int b,int c)
{
int res = 1;
while (b)
{
if (b & 1)
res = res * a % c;
a = a * a % c;
b >>= 1;
}
return res % c;
}
void init()
{
fac[0] = 1;invfac[0] = 1;
for (Int i = 1;i <= (n * 2);++ i)
{
fac[i] = fac[i - 1] * i % mod;
invfac[i] = quike_pow (fac[i],mod - 2,mod);
}
}
int C(int n,int m)
{
return fac[n] * invfac[m] % mod * invfac[n - m] % mod;
}
int Catalan (int x)
{
if (x == 0) return 1;
else return C (x << 1,x) - C (x << 1,x + 1);
}
int g(int x)
{
return Catalan (x - 1);
}
void read (int &x)
{
x = 0;char c = getchar();int f = 1;
while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}
while (c >= '0' && c <= '9'){x = (x << 3) + (x << 1) + c - '0';c = getchar();}
x *= f;return ;
}
void write (int x)
{
if (x < 0){x = -x;putchar ('-');}
if (x > 9) write (x / 10);
putchar (x % 10 + '0');
}
signed main()
{
//freopen ("R.in","r",stdin);
//freopen ("R.out","w",stdout);
read (n);
init();
dp[0][0][0] = 1;
for (Int i = 0;i <= n;++ i)
for (Int j = 0;j <= n;++ j)
if (i || j)
{
for (Int k = max(0ll,i + j - n);k <= i && k <= j;++ k)
{
int &s = dp[i][j][k];
if (i && k) s = (s + dp[i - 1][j][k - 1] * (j - k + 1) % mod) % mod;
if (j && k) s = (s + dp[i][j - 1][k - 1] * (i - k + 1) % mod) % mod;
if (i) s = (s + dp[i - 1][j][k] * (n - (i + j - k - 1)) % mod) % mod;
if (j) s = (s + dp[i][j - 1][k] * (n - (i + j - k - 1)) % mod) % mod;
for (Int d = 1;d <= k;++ d)
s = (s + mod - dp[i - d][j - d][k - d] * C (n - (i + j - k - d),d) % mod * g(d) % mod * fac[d] % mod) % mod;
}
}
write (dp[n][n][n]),putchar ('\n');
return 0;
}