[hdu 6355] Fireflies

题意

在一个\(n\)维有限超立方体\(((1, p_1), (1, p_2), ... , (1, p_n))\)中,你可以在若干个位置放一只萤火虫。

萤火虫可以行动若干次,每一次从\((x_1, x_2, x_3,...,x_n)\)走向\((y_1, y_2, y_3,...,y_n)\),都要满足:

1.起点和终点要在超立方体内;

2.\(\forall_i x_i \le y_i\)

3.\((\sum y_i - x_i) = 1\)

求最开始最少放多少个,才能确保存在一种方案,对于每一个单位空间,至少用一只萤火虫遍历它。

题解

最优解的结构可以用Dilworth定理来分析。

Dilworth定理表明,最小链覆盖数等于最大反链数,也即选出一个集合,使得集合中的位置两两不可达,要最大化这个集合的大小。

Sperner定理表明,若要选择一个集合\(S\)的幂集的一个子集使得其中没有一个集合包含在另一个集合中,则这种子集的大小最大为\(\binom {|S|}{\lfloor |S| / 2 \rfloor}\)

则Sperner定理的一个推广)恰好能给出本问题的解:

选择\(n\)维坐标之和为\(M = \lfloor \frac {1}{2} \sum_{i = 1} ^ n (p_i + 1) \rfloor\)的位置即可达到最大的数量。

则现在问题转化为求\(\sum_{i = 1} ^ n x_i = M\)\(1 \le x_i \le p_i\)的解的数量。

这是个经典问题,可以用容斥来做。

但考虑到\(n \le 32\),不能直接容斥。

容易想到可能会用到meet-in-middle。但是如何用中途相遇法呢?

考虑到如果我们容斥时选出集合\(S\)代表\(S\)中的\(x_i\)都至少比\(p_i\)大,方案数就是

\[\max(0, \binom{M - 1 - \sum_{i \in S} p_i}{n - 1}) \]

考虑到\(n\)很小,然后就有一个套路,将这个组合数看成下降幂形式,然后就成了\(\sum_{i \in S} p_i\)\(n - 1\)次多项式。那么我们可以先预处理出这个多项式的系数,然后在处理出每个集合\(S\)\(\sum_{i \in S} p_i\)(这里的\(S\)是折半枚举的),然后meet-in-middle的时候合并一下,用个前缀和优化就好了。

注意到如果\(M - 1 - \sum_{i \in S}p_i < 0\),不能用下降幂来算,这种情况方案数应该算作\(0\)(实际上在这个问题中\(\binom {n}{m}\)只要\(n < m\)就是\(0\))。处理时,可以用一个双指针来搞一搞这部分。

最终复杂度大概是\(O(2 ^ {n / 2} n ^ 2)\)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 40, mo = 1e9 + 7;
const int M = 1 << 16 | 5;
inline int read () {
    static int x;
    scanf("%d", &x);
    return x;
}
inline ll exp (ll a, ll b, ll ret = 1) {
    for (a %= mo; b; b >>= 1, a = a * a % mo)
        if (b & 1) ret = ret * a % mo;
    return ret;
}

int n, m, a[N];
int bitcnt[M];
ll k, cb[N][N], ivf[N];
ll f[N][N], coe[N];

void prework () {
    ivf[0] = 1;
    for (int i = 1; i < N; ++i)
        ivf[i] = ivf[i - 1] * exp(i, mo - 2) % mo;
    bitcnt[0] = 0;
    for (int i = 1; i < M; ++i)
        bitcnt[i] = bitcnt[i >> 1] + (i & 1);
    cb[0][0] = 1;
    for (int i = 1; i < N; ++i) {
        cb[i][0] = cb[i][i] = 1;
        for (int j = 1; j < i; ++j)
            cb[i][j] = (cb[i - 1][j - 1] + cb[i - 1][j]) % mo;
    }
}
void predo () {
    memset(f, 0, sizeof f);
    f[0][0] = n & 1 ? 1 : -1;
    for (int i = 1; i < n; ++i) {
        for (int j = 1; j < n; ++j)
            f[i][j] = f[i - 1][j - 1];
        for (int j = 0; j < n; ++j)
            f[i][j] = (f[i][j] - f[i - 1][j] * ((k - i) % mo) % mo + mo) % mo;
    }
    for (int i = 0; i < n; ++i)
        coe[i] = f[n - 1][i] * ivf[n - 1] % mo;
}

pair <ll, vector <ll> > p[M], q[M];
ll presum[N];
ll in_ex (ll ret = 0) {
    for (int s = 0; s < (1 << m); ++s) {
        ll sum = 0; int sgn = bitcnt[s] & 1 ? -1 : 1;
        for (int i = 0; i < m; ++i)
            if (s >> i & 1) sum += a[i];
        p[s].first = sum, p[s].second.resize(n, 0);
        for (int i = 0; i < n; ++i)
            p[s].second[i] = exp(sum % mo, i) * sgn % mo;
    }
    sort(p, p + (1 << m));
    for (int s = 0; s < (1 << (n - m)); ++s) {
        ll sum = 0; int sgn = bitcnt[s] & 1 ? -1 : 1;
        for (int i = 0; i < (n - m); ++i)
            if (s >> i & 1) sum += a[i + m];
        q[s].first = sum, q[s].second.resize(n, 0);
        for (int i = 0; i < n; ++i)
            q[s].second[i] = exp(sum % mo, i) * sgn % mo;
    }
    sort(q, q + (1 << (n - m)));
    memset(presum, 0, sizeof presum);
    for (int s = (1 << m) - 1, t = 0; ~s; --s) {
        for ( ; t < (1 << (n - m)) && k - n - (p[s].first + q[t].first) >= 0; ++t) {
            for (int i = 0; i < n; ++i) presum[i] = (presum[i] + q[t].second[i]) % mo;
        }
        for (int i = 0; i < n; ++i) {
            ll tmp = 0;
            for (int j = 0; j <= i; ++j)
                tmp = (tmp + cb[i][j] * p[s].second[j] % mo * presum[i - j] % mo) % mo;
            ret = (ret + coe[i] * tmp) % mo;
        }
    }
    return (ret % mo + mo) % mo;
}
signed main () {
    prework();
    for (int _ = read(); _; --_) {
        n = read(), m = (n + 1) / 2, k = 0;
        for (int i = 0; i < n; ++i)
            a[i] = read(), k += a[i] + 1;
        k /= 2;
        if (n == 1) {
            printf("1\n");
            continue;
        }
        predo();
        printf("%lld\n", in_ex());
    }
    return 0;
}
posted @ 2019-04-18 08:22  psimonw  阅读(394)  评论(0编辑  收藏  举报