Hihocoder1147时空阵题解

幽香这几天学习了魔法,准备建造一个大型的时空传送阵。

幽香现在可以在幻想乡的 $n$ 个地点建造一些传送门,如果她建造了从地点 $a$ 与地点 $b$ 之间的传送门,那么从 $a$ 到 $b$ 和从 $b$ 到 $a$ 都只需要单位 $1$ 的时间。

同时这些地点之间在地理上是非常遥远的,因此来往他们必须使用传送门。

现在幽香想要问你,有多少种建造传送门的方案,使得地点 $1$ 和地点 $n$ 之间的最短距离恰好为 $k$ ?两个方案不同当且仅当建造的传送门的集合不同。不能建造节点到自身的传送门,两个点之间也最多造一个传送门。

$n,k\leq 100$ 。

最近看dls讲的一道题,因为hihocoder可能要凉凉了,写个文章记录一下。

点 $1$ 和 $n$ 之间距离为 $k$ 说明:如果从点 $1$ 出发进行BFS,那么搜到点 $n$ 一定是第 $k$ 轮,这意味着我们可以将整张图根据从点 $1$ 开始BFS被搜到的轮数划分成一个分层图,然后在这张分层图上就可以dp了。

设 $dp[i][j][k]$ 表示分层图前 $i-1$ 层已经用了 $j$ 个点,第 $i-1$ 层用了 $k$ 个点的方案数,那么前 $k-1$ 层的转移方程如下(枚举第 $i$ 层有 $x$ 个点):

$$
dp[i][j+x][x]+=dp[i-1][j][k]\times 2^\binom{x}{2}\times (2^k-1)^{x}\times \binom{n-j-1}{x}
$$

解释如下,从第 $i-1$ 层到第 $i$ 层需要考虑的有:

  1. 连边方式
    连边方式分两类:两层之间的连边和第 $i$ 层内部的连边。第 $i-1$ 层的点数为 $k$ ,而第 $i$ 层的点数为 $x$ ,对于第 $i$ 层中的每个点而言,至少有一个前一层的点与它相邻,因此一共有 $2^{k}-1$ 种方案,同时由于第 $i$ 层的每个点独立,因此方案数为 $(2^k-1)^x$ 。第 $i$ 层内部的连边则比较简单,$x$ 个点之间一共 $\binom{x}{2}$ 条边,每条边都可以连或不连。
  2. 点的编号
    每层可以选择的点的方案数为 $\binom{n-j-1}{x}$ ,需要注意我们不能选择 $n$ 号点。

然后是第 $k$ 层的方案数,第 $k$ 层的不同点在于这一层必定有 $n$ 号点,因此该层可以选择的点的方案数为 $\binom{n-j-1}{x-1}$ 。(前 $i-1$ 层已经用了 $j$ 个点,第 $k$ 层有 $x$ 个点)

最后将所有剩余的点考虑进来,一共还剩下 $n-j-x$ 个点,这些点内部可以任意连边,并且这些点和第 $k$ 层的点也可以任意连边,方案数为 $2^{\binom{n-j-x}{2}}\times 2^{( n-j-x )x}$ 。

最后附赠一份不知道正确性的代码(hihocoder暂时无法提交。。。):

#include <bits/stdc++.h>
using namespace std;
constexpr int P = 1e9 + 7;
constexpr int N = 110;
// assume -P <= x < 2P
int norm(int x) {
    if (x < 0) {
        x += P;
    }
    if (x >= P) {
        x -= P;
    }
    return x;
}
template<class T>
T power(T a, int b) {
    T res = 1;
    for (; b; b /= 2, a *= a) {
        if (b % 2) {
            res *= a;
        }
    }
    return res;
}
struct Z {
    int x;
    Z(int x = 0) : x(norm(x)) {}
    int val() const {
        return x;
    }
    Z operator-() const {
        return Z(norm(P - x));
    }
    Z inv() const {
        assert(x != 0);
        return power(*this, P - 2);
    }
    Z& operator*=(const Z& rhs) {
        x = 1LL * x * rhs.x % P;
        return *this;
    }
    Z& operator+=(const Z& rhs) {
        x = norm(x + rhs.x);
        return *this;
    }
    Z& operator-=(const Z& rhs) {
        x = norm(x - rhs.x);
        return *this;
    }
    Z& operator/=(const Z& rhs) {
        return *this *= rhs.inv();
    }
    friend Z operator*(const Z& lhs, const Z& rhs) {
        Z res = lhs;
        res *= rhs;
        return res;
    }
    friend Z operator+(const Z& lhs, const Z& rhs) {
        Z res = lhs;
        res += rhs;
        return res;
    }
    friend Z operator-(const Z& lhs, const Z& rhs) {
        Z res = lhs;
        res -= rhs;
        return res;
    }
    friend Z operator/(const Z& lhs, const Z& rhs) {
        Z res = lhs;
        res /= rhs;
        return res;
    }
};
// dp[i][j][k] 表示前i层一共用了j个点,最后一层用了k个点的方案数
Z dp[N][N][N];
vector<Z> fac, ifac, two;
void prepareFac(int n) {
    fac.resize(n + 1);
    two.resize(n + 1);
    ifac.resize(n + 1);
    two[0] = fac[0] = 1;
    for (int i = 1; i <= n; i++) {
        fac[i] = fac[i - 1] * Z(i);
        two[i] = two[i - 1] * Z(2);
    }
    // 1/(i + 1)! * (i+1) = 1/i!
    ifac[n] = fac[n].inv();
    for (int i = n - 1; i >= 0; i--) {
        ifac[i] = ifac[i + 1] * Z(i + 1);
    }
}
Z binom(int n, int m) {
    if (n < 0 || m < 0 || n < m) return 0;
    return fac[n] * ifac[m] * ifac[n - m];
}

int main() {
    ios::sync_with_stdio(false); 
    cin.tie(nullptr);
    cout.tie(nullptr);
    int n, k;
    cin >> n >> k;
    prepareFac(n * n);
    Z inverse = Z(n - 1).inv(), res = 0;
    dp[0][1][1] = Z(1);
    for (int i = 1; i <= k; i++) { // 枚举第i层
        for (int j = i; j <= n; j++) { // 枚举当前一共已经用了j个点
            for (int x = 1; x <= j; x++) { // 枚举第i - 1层用了x个点
                for (int y = 1; y + j <= n; y++) { // 枚举第i层要加入y个点
                    // dp[i-1][j][x] -> dp[i][j+y][y]
                    Z val = dp[i - 1][j][x] * power((two[x] - Z(1)), y) * two[y * (y - 1) / 2] * binom(n - j - 1, y - (i == k));
                    if (i == k) { // 最后一层的特判
                        // 剩下(n - j - y)个点和最后一层之间随意连边 * 这些点内部随意连边
                        int m = n - j - y;
                        val *= two[m * (m - 1) / 2 + y * m];
                        res += val;
                    } else {
                        dp[i][j + y][y] += val;
                    }
                }
            }
        }
    }
    cout << res.val() << "\n";
    return 0;
}
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇