Gerald ans Giant Chess(P334)
题意:
给定一个 n ∗ m , n , m ≤ 1 e 5 n*m,n,m\leq 1e5 n∗m,n,m≤1e5的棋盘,其中有 N ≤ 2000 N\leq 2000 N≤2000个格子是黑色的,其它都是白色的,求从左上角走到左下角且不能经过黑色格子的方案数,每次选择向右或下走。
分析:
观察到
n
,
m
n,m
n,m很大但
N
N
N很小,考虑从黑色格子入手,从左上角走到右下角的方案数显然是
C
(
n
+
m
−
2
,
n
−
1
)
C(n+m-2,n-1)
C(n+m−2,n−1)。我们考虑容斥,去减掉经过了至少一个黑色格子的路线,考虑如何设置状态做到不重不漏。我们将所有黑色格子先排序,那么我们去固定我们第一个经过的黑色格子的位置,那么所有的子问题将不会有重叠。定义
f
[
i
]
f[i]
f[i]为从左上角走到第
i
i
i个黑色格子,并且途中不经过其它黑色格子的数量。转移很简单:
f
[
i
]
=
C
(
x
i
+
y
i
−
2
,
x
i
−
1
)
−
∑
j
=
0
i
−
1
f
[
j
]
∗
C
(
x
i
−
x
j
+
y
i
−
y
j
,
x
i
−
x
j
)
f[i]=C(x_i+y_i-2,x_i-1) - \sum_{j=0}^{i-1}{f[j]*C(x_i-x_j+y_i-y_j,x_i-x_j)}
f[i]=C(xi+yi−2,xi−1)−∑j=0i−1f[j]∗C(xi−xj+yi−yj,xi−xj)
我们把右下角的格子也当作黑色,那么答案即
f
[
N
+
1
]
f[N+1]
f[N+1]。
Connected Graph
题意:
求 N ≤ 50 N\leq 50 N≤50个节点的无向连通图有多少个,节点有编号。
分析:
显然
N
N
N个节点的无向图有
2
N
∗
(
N
−
1
)
/
2
2^{N*(N-1)/2}
2N∗(N−1)/2个,那么我们考虑计算出不连通的图的个数。
考虑如何设计状态不会重复。
既然是不连通的图,那么必然是若干个连通块形成的,那么我们去枚举节点1所在连通块的大小即可不会造成重复。我们定义
f
[
i
]
f[i]
f[i]为
i
i
i个点构成的无向联通图的个数。
转移的话我们去枚举节点1所在联通块的大小为
j
j
j,那么我们可以在剩下
i
−
j
i-j
i−j个节点选
j
−
1
j-1
j−1个与1构成连通块,其它节点随意。
f
[
i
]
=
2
i
∗
(
i
−
1
)
/
2
−
∑
j
=
1
i
−
1
f
[
j
]
∗
C
(
i
−
1
,
j
−
1
)
∗
2
(
i
−
j
)
∗
(
i
−
j
−
1
)
/
2
f[i]=2^{i*(i-1)/2}-\sum_{j=1}^{i-1}{f[j]*C(i-1,j-1)*2^{(i-j)*(i-j-1)/2}}
f[i]=2i∗(i−1)/2−∑j=1i−1f[j]∗C(i−1,j−1)∗2(i−j)∗(i−j−1)/2
How Many of Them?
题意:
求含有 N N N个节点构成的无向连通图中,割边恰好有 M M M条的方案数,节点编号 1 − N 1-N 1−N。
分析:
我们称不含割边的极大联通子图为双连通分量。
首先定义
F
[
i
]
[
j
]
:
F[i][j]:
F[i][j]:
i
i
i个点构成的包含
j
j
j条割边的无向图的数量。
那么我们仿照之前的做法,枚举1所在的双连通分量的大小
k
k
k,方案数为
F
[
k
,
0
]
∗
C
(
i
−
k
,
k
−
1
)
F[k,0]*C(i-k,k-1)
F[k,0]∗C(i−k,k−1)。
如果把图中的双连通分量都看成点的话,那么最终形成一颗树,树边连接各个双连通分量。那么我们将节点1所在联通分量从图中删去,那么图将会拆分成若干个联通块。
我们令
G
[
i
,
j
,
k
]
G[i,j,k]
G[i,j,k]表示由
i
i
i个点构成的,包含
j
j
j个联通块和
k
k
k条割边的无向图数量,同时从每个联通块中选出1个点与节点1所在连通块连接。
那么我们枚举将1所在双连通分量删去后图中连通块的数量
x
x
x,因为每一个连通块之前都与节点1所在连通块通过割边相连,所有已经消耗
x
x
x条割边了。同时,因为节点1的双连通分量有
k
k
k个节点,所以每个连通块都可以选择任意一个节点相连,所以有如下转移:
F
[
i
]
[
j
]
=
∑
k
=
1
i
−
1
(
F
[
k
,
0
]
∗
C
(
i
−
1
,
k
−
1
)
∗
∑
x
=
1
m
i
n
(
i
−
k
,
j
)
G
[
i
−
k
,
x
,
j
−
x
]
∗
k
x
)
,
j
>
0
F[i][j]=\sum_{k=1}^{i-1}{(F[k,0]*C(i-1,k-1)*\sum_{x=1}^{min(i-k,j)}{G[i-k,x,j-x]*k^x})},j>0
F[i][j]=k=1∑i−1(F[k,0]∗C(i−1,k−1)∗x=1∑min(i−k,j)G[i−k,x,j−x]∗kx),j>0
特别的:定义
H
[
i
]
H[i]
H[i]为
i
i
i个节点构成的无向连通图的数量,在上题中我们已经求得。
那么:
F
[
i
]
[
j
]
=
H
[
i
]
−
∑
j
=
1
i
−
1
F
[
i
]
[
j
]
F[i][j]=H[i]-\sum_{j=1}^{i-1}{F[i][j]}
F[i][j]=H[i]−∑j=1i−1F[i][j]。
最后考虑
G
[
i
]
[
j
]
[
k
]
G[i][j][k]
G[i][j][k]的转移:我们以编号最小的节点所在连通块为基准,枚举连通块大小
p
p
p,割边数量
q
q
q。则:
G
[
i
]
[
j
]
[
k
]
=
∑
p
=
1
i
∑
q
=
0
k
F
[
p
,
q
]
∗
C
(
i
−
1
,
p
−
1
)
∗
p
∗
G
[
i
−
p
,
j
−
1
,
k
−
q
]
G[i][j][k]=\sum_{p=1}^{i}\sum_{q=0}^{k}F[p,q]*C(i-1,p-1)*p*G[i-p,j-1,k-q]
G[i][j][k]=p=1∑iq=0∑kF[p,q]∗C(i−1,p−1)∗p∗G[i−p,j−1,k−q]
code:
mint H[55], f[55][55], g[55][55][55];
bool vis_f[55][55], vis_g[55][55][55];
mint get_f(int i, int j);
mint get_g(int i, int j, int k) {
if(j > i || k >= i) return 0;
if(vis_g[i][j][k]) return g[i][j][k];
g[i][j][k] = 0;
vis_g[i][j][k] = 1;
if(j == 1) return g[i][j][k] = get_f(i, k) * i;
for(int p = 1; p <= i; p++) {
for(int q = 0; q <= k; q++)
g[i][j][k] += get_f(p, q) * C(i - 1, p - 1) * p * get_g(i - p, j - 1, k - q);
}
return g[i][j][k];
}
mint get_f(int i, int j) {
if(j >= i) return 0;
if(vis_f[i][j]) return f[i][j];
f[i][j] = 0;
vis_f[i][j] = 1;
if(j == 0) {
f[i][j] = H[i];
for(int k = 1; k <= i - 1; k++)
f[i][j] -= get_f(i, k);
return f[i][j];
}
for(int k = 1; k < i; k++) {
mint s = 0;
for(int x = 1; x <= min(i - k, j); x++)
s += get_g(i - k, x, j - x) * mint(k).pow(x);
s = get_f(k, 0) * C(i - 1, k - 1) * s;
f[i][j] += s;
}
return f[i][j];
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
init();
H[1] = 1;
for(int i = 2; i <= 50; i++) {
H[i] = mint(2).pow(i * (i - 1) / 2);
for(int j = 1; j <= i - 1; j++)
H[i] -= H[j] * C(i - 1, j - 1) * mint(2).pow((i - j) * (i - j - 1) / 2);
}
for(int i = 0; i <= 50; i++)
for(int j = 0; j <= 50; j++)
f[i][j] = 0;
for(int i = 0; i <= 50; i++)
for(int j = 0; j <= 50; j++)
for(int k = 0; k <= 50; k++)
g[i][j][k] = 0;
f[1][0] = g[1][1][0] = 1;
vis_f[1][0] = vis_g[1][1][0] = 1;
int n, m;
cin >> n >> m;
mint ans = 0;
for(int i = 0; i <= m; i++) {
ans += get_f(n, i);
// cout << i << ' ' << get_f(n, i) << '\n';
}
cout << ans << '\n';
return 0;
}