前言
TC百题计划,走起!
题目描述
今有一图青山绿水,山神安山NN座。重峦叠嶂,峰峰起伏如此:
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≤501≤N≤50
分析
PART 1 找山峰
通过给出的图,我们可以先确定有一些地方是一定是有山峰的,假设为peakspeaks个位置,那么如何去算这个peakspeaks呢?
容易发现,一个点如果是山峰,当且仅当这个点是这一列最高点并且两边的最高点都不高于它。
这样就可以用非常简单的方法先求出peakspeaks。
PART 2 转化问题
我们设一个共有tottot个格子是山,总共有N0N0座山,还有NN座山山峰的位置不确定(即是N0−peaksN0−peaks,注意这里的记法与题目给出的有所不同)。那么问题就转化成了我们给这tottot个格子从11~编号,有N0N0个相同的物品,一个格子可以放多个物品。有peakspeaks个格子至少要放一个。放完后,我们把每个物品放到盒子的编号提出来变成一个序列,求这个序列的种数。
刚刚应该注意到非常重要也是非常关键的一点,我们结合这个样例,因为盒子的序号顺序有差异的序列也算是不同的,因此,事实上可以把这N0N0个物品看作是不相同的,然后求它们放在盒子里的方案数。
问题已经到了这一步了,就可以开始思考容斥的做法了。
PART 3 容斥
按照套路,我们设g(x)g(x)为恰好有xx个有限定的盒子没有满足限定(即这个盒子里没放一个物品)。
那么要求的答案就是totN0−g(1)−g(2)−...−g(picks)totN0−g(1)−g(2)−...−g(picks),即,总共的方案数是N0N0个物品,每个物品有都有tottot个盒子可以放,再减去恰好是11个盒子不满足条件,个盒子不满足条件,…,pickspicks个盒子不满足条件的方案数。
当然g(x)g(x)不好求,那么我们可以设一个f(x)f(x)表示至少有xx个有限定的盒子没有满足限定。那么容易得出,即,先选出xx个盒子不放任何东西,这样还剩下个盒子,也就是这N0N0个物品的选择。
再来看一看f(x)f(x)与g(x)g(x)的关系:f(x)=∑picksk=x(kx)g(k)f(x)=∑k=xpicks(kx)g(k),即,对于恰好kk个限定盒不满足的情况,这个位置的任意xx组合都会在中被计算g(k)g(k)次。
这样答案就可以这样表示:totN0−f(1)+f(2)−f(3)+...+(−1)picksf(picks)totN0−f(1)+f(2)−f(3)+...+(−1)picksf(picks),即:
(感谢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大佬还是一眼就看出来了,个人认为自己的抽象能力还有待提升。解决了计数,找山峰其实也是很一个很毒瘤的模拟题,一开始写的很麻烦,改来改去改了三次才想到这个简单的思路。总而言之,这题考验的还是你的抽象能力,是否能发现问题最本质的性质,无论是在求山峰还是算方案的时候都是很需要这种能力的。我认为这是道好题!