前言
TC百题计划,走起!
题目描述
今有一图青山绿水,山神安山 N N 座。重峦叠嶂,峰峰起伏如此:
For 0 <= i < N:
For 0 <= x < W:
For 0 <= y <= Y[i] - |x - X[i]|:
pix[x, y] := 'X'
即是:
..X...
.XXXX.
XXXXXX
如是,山峰坐标多变莫测。今予你山水画一幅,请求山峰坐标的不同序列,答案对取模。
Example
{“X.”,
“XX”}
2
Returns: 5
Here one of the mountains is completely covered by the other. The five possible sequences are:
(0, 1), (0, 1)
(0, 1), (0, 0)
(0, 1), (1, 0)
(0, 0), (0, 1)
(1, 0), (0, 1)
数据范围
- 画布长宽皆至五十
- 1≤N≤50 1 ≤ N ≤ 50
分析
PART 1 找山峰
通过给出的图,我们可以先确定有一些地方是一定是有山峰的,假设为
peaks
p
e
a
k
s
个位置,那么如何去算这个
peaks
p
e
a
k
s
呢?
容易发现,一个点如果是山峰,当且仅当这个点是这一列最高点并且两边的最高点都不高于它。
这样就可以用非常简单的方法先求出
peaks
p
e
a
k
s
。
PART 2 转化问题
我们设一个共有
tot
t
o
t
个格子是山,总共有
N0
N
0
座山,还有
N
N
座山山峰的位置不确定(即是
N0−peaks
N
0
−
p
e
a
k
s
,注意这里的记法与题目给出的有所不同)。那么问题就转化成了我们给这
tot
t
o
t
个格子从
1
1
~编号,有
N0
N
0
个相同的物品,一个格子可以放多个物品。有
peaks
p
e
a
k
s
个格子至少要放一个。放完后,我们把每个物品放到盒子的编号提出来变成一个序列,求这个序列的种数。
刚刚应该注意到非常重要也是非常关键的一点,我们结合这个样例,因为盒子的序号顺序有差异的序列也算是不同的,因此,事实上可以把这
N0
N
0
个物品看作是不相同的,然后求它们放在盒子里的方案数。
问题已经到了这一步了,就可以开始思考容斥的做法了。
PART 3 容斥
按照套路,我们设
g(x)
g
(
x
)
为恰好有
x
x
个有限定的盒子没有满足限定(即这个盒子里没放一个物品)。
那么要求的答案就是
totN0−g(1)−g(2)−...−g(picks)
t
o
t
N
0
−
g
(
1
)
−
g
(
2
)
−
.
.
.
−
g
(
p
i
c
k
s
)
,即,总共的方案数是
N0
N
0
个物品,每个物品有都有
tot
t
o
t
个盒子可以放,再减去恰好是
1
1
个盒子不满足条件,个盒子不满足条件,…,
picks
p
i
c
k
s
个盒子不满足条件的方案数。
当然
g(x)
g
(
x
)
不好求,那么我们可以设一个
f(x)
f
(
x
)
表示至少有
x
x
个有限定的盒子没有满足限定。那么容易得出,即,先选出
x
x
个盒子不放任何东西,这样还剩下个盒子,也就是这
N0
N
0
个物品的选择。
再来看一看
f(x)
f
(
x
)
与
g(x)
g
(
x
)
的关系:
f(x)=∑picksk=x(kx)g(k)
f
(
x
)
=
∑
k
=
x
p
i
c
k
s
(
k
x
)
g
(
k
)
,即,对于恰好
k
k
个限定盒不满足的情况,这个位置的任意
x
x
组合都会在中被计算
g(k)
g
(
k
)
次。
这样答案就可以这样表示:
totN0−f(1)+f(2)−f(3)+...+(−1)picksf(picks)
t
o
t
N
0
−
f
(
1
)
+
f
(
2
)
−
f
(
3
)
+
.
.
.
+
(
−
1
)
p
i
c
k
s
f
(
p
i
c
k
s
)
,即:
(感谢 cly_none大佬指教)
参考程序
//tc is healthy, just do it
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MOD = 1000000009;
const int ArSize = 55;
int fac[ArSize], inv[ArSize];
class MountainsEasy {
private:
int H, W;
void init() { /*预处理出50以内的阶乘及其逆元,后面算组合数会用到*/
int i;
for (fac[0] = i = 1; i <= 50; i++) fac[i] = (LL)i * fac[i - 1] % MOD;
inv[50] = fast_pow(fac[50], MOD - 2);
for (i = 49; i >= 0; i--) inv[i] = (LL)(i + 1) * inv[i + 1] % MOD;
}
int fast_pow(int bs, int ex) { // 快速幂
int res = 1;
for (; ex > 0; ex >>= 1, bs = (LL)bs * bs % MOD) if (ex & 1) res = (LL)res * bs % MOD;
return res;
}
int C(int n, int r) { // 组合数
return (LL)fac[n] * inv[r] % MOD * inv[n - r] % MOD;
}
public:
int countPlacements( vector <string> picture, int N );
};
int MountainsEasy::countPlacements(vector <string> picture, int N) {
H = picture.size(), W = picture[0].length();
int x, y, N0 = N;
for (x = 0; x < W && picture[H - 1][x] == '.'; x++);
if (x == W) return 0;
else {
int tot = 0;
// 找确定的山峰个数
for (; x < W; x++) {
for (y = 0; y < H && picture[y][x] == '.'; y++);
if (y == H) continue;
if (!y ||
(!x && (x + 1 == W || picture[y - 1][x + 1] == '.')) ||
(x + 1 == W && (!x || picture[y - 1][x - 1] == '.')) ||
(x && x + 1 < W && picture[y - 1][x - 1] == '.' && picture[y - 1][x + 1] == '.')
)
--N;
tot += H - y;
}
if (N < 0) return 0;
init();
int peaks = N0 - N;
LL res = 0;
// 容斥部分,极其精简吧
for (int i = 0; i <= peaks; i++)
if (i & 1) res = (res + MOD - (LL)C(peaks, i) * fast_pow(tot - i, N0) % MOD) % MOD;
else res = (res + (LL)C(peaks, i) * fast_pow(tot - i, N0) % MOD) % MOD;
return (int)res;
}
}
总结
首先这个原题的英文题面废话很多,很长,转化之后就很简单的一个意思。不过转化到物品不同这一性质其实不容易,因为之前抽象出来的问题描述的不是很好…(我的锅…)但是cly大佬还是一眼就看出来了,个人认为自己的抽象能力还有待提升。解决了计数,找山峰其实也是很一个很毒瘤的模拟题,一开始写的很麻烦,改来改去改了三次才想到这个简单的思路。总而言之,这题考验的还是你的抽象能力,是否能发现问题最本质的性质,无论是在求山峰还是算方案的时候都是很需要这种能力的。我认为这是道好题!