TC SRM567 DIV2 T3 MountainsEasy

前言

TC百题计划,走起!

题目描述

今有一图青山绿水,山神安山 N 座。重峦叠嶂,峰峰起伏如此:

For 0 <= i < N:
    For 0 <= x < W:
        For 0 <= y <= Y[i] - |x - X[i]|:
            pix[x, y] := 'X'

即是:

..X...
.XXXX.
XXXXXX

如是,山峰坐标多变莫测。今予你山水画一幅,请求山峰坐标的不同序列,答案对 10 9 + 9 取模。

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

分析

PART 1 找山峰

通过给出的图,我们可以先确定有一些地方是一定是有山峰的,假设为 p e a k s 个位置,那么如何去算这个 p e a k s 呢?
容易发现,一个点如果是山峰,当且仅当这个点是这一列最高点并且两边的最高点都不高于它。
这样就可以用非常简单的方法先求出 p e a k s

PART 2 转化问题

我们设一个共有 t o t 个格子是山,总共有 N 0 座山,还有 N 座山山峰的位置不确定( N 即是 N 0 p e a k s ,注意这里的记法与题目给出的有所不同)。那么问题就转化成了我们给这 t o t 个格子从 1 ~ t o t 编号,有 N 0 个相同的物品,一个格子可以放多个物品。有 p e a k s 个格子至少要放一个。放完后,我们把每个物品放到盒子的编号提出来变成一个序列,求这个序列的种数。
刚刚应该注意到非常重要也是非常关键的一点,我们结合这个样例,因为盒子的序号顺序有差异的序列也算是不同的,因此,事实上可以把这 N 0 个物品看作是不相同的,然后求它们放在盒子里的方案数。
问题已经到了这一步了,就可以开始思考容斥的做法了。

PART 3 容斥

按照套路,我们设 g ( x ) 为恰好有 x 个有限定的盒子没有满足限定(即这 x 个盒子里没放一个物品)。
那么要求的答案就是 t o t N 0 g ( 1 ) g ( 2 ) . . . g ( p i c k s ) ,即,总共的方案数是 N 0 个物品,每个物品有都有 t o t 个盒子可以放,再减去恰好是 1 个盒子不满足条件, 2 个盒子不满足条件,…, p i c k s 个盒子不满足条件的方案数。
当然 g ( x ) 不好求,那么我们可以设一个 f ( x ) 表示至少有 x 个有限定的盒子没有满足限定。那么容易得出 f ( x ) = ( p i c k s x ) × ( t o t x ) N 0 ,即,先选出 x 个盒子不放任何东西,这样还剩下 t o t x 个盒子,也就是这 N 0 个物品的选择。
再来看一看 f ( x ) g ( x ) 的关系: f ( x ) = k = x p i c k s ( k x ) g ( k ) ,即,对于恰好 k 个限定盒不满足的情况,这 k 个位置的任意 x 组合都会在 f ( x ) 中被计算 g ( k ) 次。
这样答案就可以这样表示: t o t N 0 f ( 1 ) + f ( 2 ) f ( 3 ) + . . . + ( 1 ) p i c k s f ( p i c k s ) ,即:

i = 0 p e a k s ( p e a k s i ) × ( 1 ) i × ( t o t i ) N 0

(感谢 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大佬还是一眼就看出来了,个人认为自己的抽象能力还有待提升。解决了计数,找山峰其实也是很一个很毒瘤的模拟题,一开始写的很麻烦,改来改去改了三次才想到这个简单的思路。总而言之,这题考验的还是你的抽象能力,是否能发现问题最本质的性质,无论是在求山峰还是算方案的时候都是很需要这种能力的。我认为这是道好题!

猜你喜欢

转载自blog.csdn.net/HelloHitler/article/details/81583858