wait
一、题目及数据范围
题目描述 n ∗ m n*m n∗m有一的棋盘,每次随机染黑一个位置(可能染到已经黑了的),当某一行或者一列全为黑色时停止,求期望染色次数(mod 998244353)。
二、解法
期我打模拟退火都骗不了分,期望的题都很毒瘤。
我们需要这样转化问题:
假设涂色时在涂完某一行或某一列是停止,则可以设
{
a
i
}
\{ai\}
{ai}
(
1
≤
i
≤
n
)
(1\le i\le n)
(1≤i≤n) 为第i行全部涂色的时间,
{
b
i
}
\{bi\}
{bi}
(
1
≤
i
≤
m
)
(1\le i \le m)
(1≤i≤m),为第i列全部涂色的时间。
设
U
=
{
a
i
}
∪
{
b
i
}
U=\{ai\}\cup \{bi\}
U={ai}∪{bi},答案为ans,则可以得到
a
n
s
=
m
i
n
{
v
∣
v
⊆
U
}
ans=min\{v\vert v \subseteq U \}
ans=min{v∣v⊆U}。这里的都是期望值。
又发现做不动了,怎么办呢?
是时候介绍我们的Min-Max反演!
m
i
n
{
v
∣
v
⊆
U
}
=
∑
S
⊆
U
∧
S
≠
∅
(
−
1
)
s
+
1
m
a
x
{
t
∣
t
⊆
S
}
min\{v\vert v \subseteq U \}=\sum_{S\subseteq U \wedge S\not = \emptyset}(-1)^{s+1}max\{t\vert t\subseteq S \}
min{v∣v⊆U}=∑S⊆U∧S=∅(−1)s+1max{t∣t⊆S}
要证明这个是正确的,我们只需要保证右边这一坨中除了最小值以外,其它的都被消掉了。
我们先对
U
U
U 里面的元素排序。然后,对于
U
U
U 中排序后的第
i
i
i 个元素
u
i
ui
ui ,讨论:
1.
i
=
1
i=1
i=1 ,此时会留下一个
u
1
u1
u1 。
2.
i
≠
1
i\not = 1
i=1 ,则可以确定,计算到
u
i
ui
ui 的子集中只会有小于等于
u
i
ui
ui 的元素。实际上就是
u
1
u1
u1 到
u
i
−
1
ui−1
ui−1 这个集合的子集并上
u
i
ui
ui ,共
2
i
−
1
2^{i-1}
2i−1;其中集合大小为奇数的有
2
i
−
2
2^{i-2}
2i−2 个,为偶数的有
2
i
−
2
2^{i-2}
2i−2 个,抵消之后不会被计入答案。
所以该反演是正确的。
回到原题,我们可以用反演将式子变形。
a
n
s
=
m
i
n
{
v
∣
v
⊆
U
}
=
∑
S
⊆
U
∧
S
≠
∅
(
−
1
)
s
+
1
m
a
x
{
t
∣
t
⊆
S
}
ans=min\{v\vert v \subseteq U \}=\sum_{S\subseteq U \wedge S\not = \emptyset}(-1)^{s+1}max\{t\vert t\subseteq S \}
ans=min{v∣v⊆U}=∑S⊆U∧S=∅(−1)s+1max{t∣t⊆S}
根据期望的线性(可加性),可得
E
(
a
n
s
)
=
E
(
m
i
n
{
v
∣
v
⊆
U
}
)
=
E
(
∑
S
⊆
U
∧
S
≠
∅
(
−
1
)
s
+
1
m
a
x
{
t
∣
t
⊆
S
}
)
E(ans)=E(min\{v\vert v \subseteq U \})=E(\sum_{S\subseteq U \wedge S\not = \emptyset}(-1)^{s+1}max\{t\vert t\subseteq S \})
E(ans)=E(min{v∣v⊆U})=E(∑S⊆U∧S=∅(−1)s+1max{t∣t⊆S})
那么问题就转化成了求
E
(
∑
S
⊆
U
∧
S
≠
∅
(
−
1
)
s
+
1
m
a
x
{
t
∣
t
⊆
S
}
)
E(\sum_{S\subseteq U \wedge S\not = \emptyset}(-1)^{s+1}max\{t\vert t\subseteq S \})
E(∑S⊆U∧S=∅(−1)s+1max{t∣t⊆S})。
这就相当于选
i
i
i行
j
j
j列,求把这里面包含的格子染色的期望时间。也就是染色
i
∗
m
+
j
∗
n
−
i
∗
j
i*m+j*n-i*j
i∗m+j∗n−i∗j个格子的期望时间。设
k
k
k为需要染色的格子总数:
当
k
=
1
k=1
k=1时,
E
=
m
∗
n
E=m*n
E=m∗n(即染
m
∗
n
m*n
m∗n次期望能染到那个格子)
当
k
=
2
k=2
k=2时,
E
=
m
∗
n
+
m
∗
n
/
2
E=m*n+m*n/2
E=m∗n+m∗n/2(即先花
m
∗
n
/
2
m*n/2
m∗n/2次染一个格子,就变成了
k
=
1
k=1
k=1,期望相加)
以此类推…
我们可以发现涂
k
k
k个格子的期望为:
E
=
∑
i
=
1
k
n
∗
m
/
i
E=\sum_{i=1}^{k} n*m/i
E=∑i=1kn∗m/i
然后就对这个反演进行求和,注意统计本质不同的方案数,时间复杂度
O
(
n
∗
m
)
O(n*m)
O(n∗m),请看代码。
#include <cstdio>
const int MOD = 998244353;
const int MAXN = 1005;
int read()
{
int x=0,flag=1;
char c;
while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*flag;
}
int n,m,ans,fac[MAXN],finv[MAXN],inv[MAXN*MAXN],f[MAXN*MAXN];
int max(int a,int b)
{
return a>b?a:b;
}
int C(int a,int b)
{
return 1ll*fac[b]*finv[a]%MOD*finv[b-a]%MOD;
}
int main()
{
n=read();
m=read();
inv[1]=1;
f[1]=n*m;
for(int i=2; i<=n*m; i++)
{
inv[i]=1ll*(MOD-MOD/i)*inv[MOD%i]%MOD;
f[i]=(f[i-1]+1ll*n*m*inv[i]%MOD)%MOD;
}
finv[0]=fac[0]=1;
for(int i=1; i<=max(n,m); i++)
{
fac[i]=1ll*fac[i-1]*i%MOD;
finv[i]=1ll*inv[i]*finv[i-1]%MOD;
}
for(int i=0; i<=n; i++)
for(int j=0; j<=m; j++)
if(i|j)
{
int a=i*m+j*n-i*j;
int t=1ll*C(i,n)*C(j,m)%MOD*f[a]%MOD;
ans=(1ll*ans+t*((i+j)&1?1:-1)+MOD)%MOD;
}
printf("%d\n",ans);
}
游戏
一、题目及数据范围
双方进行游戏,有两个正整数a,b 若a<=b 双方轮流进行选择下列两种操作之一 1)将b对a取模,即b变为b%a 2)从b中减去a的幂,不能减成负数,即b变为 b − a k ( b − a k ≤ 0 b-a^k(b-a^k\leq 0 b−ak(b−ak≤0且k为正整数) 若a>b,类似 若a,b中之一为0,则无法进行,无法进行者败
输入初始a,b,判断先后手谁有必胜策略
二、解法
UPD 2020-8-24:把这道神题重新梳理一下
不妨设 a < b a<b a<b,由于我们有两种操作,而且 s g sg sg函数也不好定义,我们干脆先只考虑第一种操作,然后再结合第二种操作。我们写的是 d f s dfs dfs,如果无脑辗转相除返回的是先手必输的话,那么当前的先手就赢了,直接返回。现在要考虑辗转相除后先手必胜的情况,也就是状态 { a , b % a } \{a,b\% a\} {a,b%a}是必胜态,我们想知道 { a , b } \{a,b\} {a,b}的状态。
想一想它和什么东西有关系,我们可以把要求的状态转移到 { a , b % a } \{a,b\%a\} {a,b%a}去,转移的过程应该是只和 a b \frac{a}{b} ba,从他入手!
设 k = a b k=\frac{a}{b} k=ba,设 f ( k ) = 0 / 1 f(k)=0/1 f(k)=0/1为先手必输还是先手必胜的函数,我们已经知道了 f ( 0 ) = 1 f(0)=1 f(0)=1,转移就是 f ( k ) = f ( k − a i ) f(k)=f(k-a^i) f(k)=f(k−ai),你首先需要知道一个优美的结论: a i = { 1 , a } m o d ( a + 1 ) a^i=\{1,a\}\mod (a+1) ai={1,a}mod(a+1),神奇的地方在于两种余数之和等于模数
不要急,我们先来看 k ≤ a k\leq a k≤a的情况:
- 对于 k ≠ a k\not=a k=a,只能从 k − 1 k-1 k−1转移,那么 f ( k ) = ! f ( k − 1 ) f(k)=!f(k-1) f(k)=!f(k−1)
- 对于 k = a k=a k=a,由于 0 0 0已经是先手必胜了,拿他的状态肯定先手必败,那么 f ( k ) = ! f ( k − 1 ) f(k)=!f(k-1) f(k)=!f(k−1)
这类情况就很好判断了(只用模
2
2
2的余数),打表发现 一个结论:
f
(
k
)
=
f
(
k
m
o
d
(
a
+
1
)
)
f(k)=f(k\mod(a+1))
f(k)=f(kmod(a+1)),证明(多看几遍看上面加粗的部分,下面取几的含义是取的东西的余数):
- f ( k m o d ( a + 1 ) ) = 0 f(k\mod(a+1))=0 f(kmod(a+1))=0,那么后手一定让你拿到那个状态,先手无法反制。
- f ( k m o d ( a + 1 ) ) = 1 f(k\mod(a+1))=1 f(kmod(a+1))=1,由于 f ( k m o d ( a + 1 ) − 1 ) = 0 f(k\mod(a+1)-1)=0 f(kmod(a+1)−1)=0,那么先手先取一个 1 1 1在给后手,后手无法反制地拿到了必败状态。
- 需要特别考虑 k m o d ( a + 1 ) = 0 k\mod(a+1)=0 kmod(a+1)=0(总不能减成负数吧),先手取一个 a a a,后手取到 f ( k − a ) f(k-a) f(k−a)就输掉了(因为 k − a m o d ( a + 1 ) = 1 k-a\mod(a+1)=1 k−amod(a+1)=1,那么就转移到了 f ( 1 ) f(1) f(1),也就是后手必败)
这真的是人能想出来的?但是代码却是人能写出来的
#include <cstdio>
#include <iostream>
#define LL long long
using namespace std;
const LL MOD = 998244353;
LL read()
{
LL x=0,flag=1;
char c;
while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*flag;
}
LL T,a,b;
LL DFS(LL a,LL b)
{
if(a>b) swap(a,b);
if(a==0) return 0;
if(!DFS(b%a,a)) return 1;
LL k=(b/a)%(a+1);
return (k&1)==0;
}
int main()
{
T=read();
while(T--)
{
a=read();
b=read();
if(DFS(a,b))
puts("First");
else
puts("Second");
}
}
R
一、题目及数据范围
题目描述 有两个 1 ∼ n 1\sim n 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
二、解法
设计
d
p
[
i
]
[
j
]
[
k
]
dp[i][j][k]
dp[i][j][k]为填第一个数列填
i
i
i个,第二个数列填
j
j
j个,有
k
k
k个数重复的方案数。
我们很轻易的能写出下列转移:
int &s=dp[i][j][k];
if(i&&k) s=(s+1ll*dp[i-1][j][k-1]*(j-k+1))%MOD;
if(j&&k) s=(s+1ll*dp[i][j-1][k-1]*(i-k+1))%MOD;
if(i) s=(s+1ll*dp[i-1][j][k]*(n-i-j+k+1))%MOD;
if(j) s=(s+1ll*dp[i][j-1][k]*(n-i-j+k+1))%MOD;
难道这道题真的只有这么简单吗?肯定不会。
我们考虑对方案数去重,我们选出
d
d
d个不同的重复数,这样的
C
(
n
−
i
−
j
+
k
+
d
,
d
)
C(n-i-j+k+d,d)
C(n−i−j+k+d,d),即除了序列中单独出现的数都可以选,再对这
d
d
d个数全排,乘上
d
p
[
i
−
d
]
[
j
−
d
]
[
j
−
d
]
dp[i-d][j-d][j-d]
dp[i−d][j−d][j−d]。最后要计算这
2
d
2d
2d个数能生成的序列个数,如果直接用
C
(
2
d
,
d
)
C(2d,d)
C(2d,d),肯定会多去重。我们定义好串为不满足
[
1
,
i
]
[1,i]
[1,i]中每个数都出现两次的生成串,我们自然想起了在一个
n
∗
n
n*n
n∗n的矩形,从左下角到右上角(把移动看成取数),不经过对角线的方案数(我们这里要将对角线向下平移一格,因为我们不能取到对角线),我们就用
C
(
2
d
,
d
)
−
C
(
2
d
,
d
+
1
)
C(2d,d)-C(2d,d+1)
C(2d,d)−C(2d,d+1)计算数量。
下面看代码吧。
#include <cstdio>
const int MOD = 998244353;
const int MAXN = 105;
int read()
{
int x=0,flag=1;
char c;
while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*flag;
}
int n,fac[MAXN*2]= {1},inv[MAXN*2]= {1,1};
int dp[MAXN][MAXN][MAXN];
int max(int a,int b)
{
return a>b?a:b;
}
void init()
{
for(int i=2; i<=n*2; i++)
inv[i]=1ll*(MOD-MOD/i)*inv[MOD%i]%MOD;
for(int i=1; i<=n*2; i++)
{
fac[i]=1ll*fac[i-1]*i%MOD;
inv[i]=1ll*inv[i]*inv[i-1]%MOD;
}
}
int C(int n,int m)
{
return 1ll*fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}
int ctl(int x)
{
if(x==0) return 1;
return C(x<<1,x)-C(x<<1,x+1);
}
int g(int x)
{
return ctl(x-1);
}
int main()
{
n=read();
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(0,i+j-n); k<=i && k<=j; k++)
{
int &s=dp[i][j][k];
if(i&&k) s=(s+1ll*dp[i-1][j][k-1]*(j-k+1))%MOD;
if(j&&k) s=(s+1ll*dp[i][j-1][k-1]*(i-k+1))%MOD;
if(i) s=(s+1ll*dp[i-1][j][k]*(n-i-j+k+1))%MOD;
if(j) s=(s+1ll*dp[i][j-1][k]*(n-i-j+k+1))%MOD;
for(int d=1; d<=k; d++)
s=(s+MOD-1ll*dp[i-d][j-d][k-d]*C(n-i-j+k+d,d)%MOD*g(d)%MOD*fac[d]%MOD)%MOD;
}
printf("%d\n",dp[n][n][n]);
}