@雅礼集训01/13 - T1@ union


@description@

已知 n 个点,点 i 与点 j 有 C(i, j) 种不同的连边方式(这个不是组合数!)。
求最终可能的不同连通图个数。

input
第一行一个正整数 n. n <= 20.
接下来 n − 1 行, 第 i 行 n − i 个正整数, 第 j 个数表示 C(i, i+j) .
output
一行一个整数, 表示答案对 10^9 + 7 取模的结果.

sample input
3
2 3
4
sample output
50

@solution@

@part - 1@

数据范围明显暗示状压。
定义 \(f(s)\) 表示集合状态为 s 的连通图个数,我们考虑怎么 dp 去求解 \(f(s)\)
一个常用的操作:容斥。我们再定义 \(g(s)\) 表示集合状态为 s 的管你连不连通的图。则有 dp 转移式(其实也就是容斥):
\[f(s) = g(s) - \sum_{t\subset s}f(t)*g(s-t)\]
其中为了避免重复计算,我们还要求 s 中的编号最小的点与 t 中的编号最小的点相同。
既然是容斥,放宽了限制,那么 g 也很好求:
\[g(s)=\prod_{i<j且i,j\in s}(C(i,j)+1),g(0)=0\]
由于涉嫌枚举子集,这是一个 O(3^n) 的 dp。考虑怎么进行优化。

@part - 2@

我们是要求解全集 s 的 dp 值,又要求编号最小的点的 dp 才能转移到全集 s。因此我们所有有用的 dp 值必须包含 1 号点。我们就可以把所有状态右移一位,省去同时包含编号最小的点这一限制。

这样 dp 转移式变为:
\[f'(s) = h(s) - \sum_{t\subset s}f'(t)*g'(s-t)\]
注意 h 和 g' 是不一样的,前者必须要包含 1 号点,后者不能包含一号点。

长得一幅子集卷积模样。我们用卷积的形式再来写一遍式子:
\[f' = h - f'*g'\]
基本的代数变形:
\[f' = h*(g'+1)^{-1}\]
那么我们就是要来求解子集卷积的逆运算。

@part - 3@

怎么求解子集卷积的逆运算呢?我们先来看看子集卷积本身:
\[f(s)=\sum_{t\subset s}g(t)*h(s-t)\]
我们讨论二进制表示下 1 的个数。令 \(f_i(s)\) 表示二进制表示下 1 的个数为 i 的集合状态为 s 的值,如果 s 二进制表示下 1 的个数不等于 i 则该值为 0。
好。那么我们可以直接用 \(g_i\)\(h_j\) 卷积卷出 \(f_{i+j}\),然后把 \(f_{i+j}\) 中不合法的集合状态再全部赋值为 0。

假如我们先 FWT/FMT 做完正变换,然后卷积式子变为:
\[\hat f_{i}(s) = \sum_{p+q=i}\hat g_p(s)*\hat h_q(s)\]
这是一个多项式加法卷积。但是我们可以直接暴力 O(n^2) 做(因为 n 只有 20 啊)。
那么我们考虑逆变换。即已知 \(\hat f_i\)\(\hat g_p\)\(\hat h_q\)

这是一个多项式除法,但是我们还是直接暴力 O(n^2) 做(都说了 n 只有 20 啊)。
首先:\(\hat f_0(s) = \hat g_0(s)*\hat h_0(s)\)。因此可以直接求解出 \(\hat g_0(s)\)
然后假设我们已经求解出了 \(g_{0...i-1}(s)\)
根据公式: \(\hat f_i(s) = \hat g_0(s)*\hat h_i(s) + \dots + \hat g_{i-1}(s)h_{1}(s)+\hat g_i(s)*\hat h_0(s)\)
因此我们可以把 \(\hat g_i(s)*\hat h_0(s)\) 前面那一团东西暴力求解然后移项,再两边作个除法,就可以求解出 \(g_i(s)\)

@accepted code@

卡过常……因此有些地方可能有点儿丑陋。

#include<cstdio>
#define rg register
const int MOD = int(1E9) + 7;
const int MAXN = 20;
int C[MAXN][MAXN], lg[1<<MAXN], bits[1<<MAXN];
inline int lowbit(int x) {return x & -x;}
inline int pow_mod(int b, int p) {
    int ret = 1;
    while( p ) {
        if( p & 1 ) ret = 1LL*ret*b%MOD;
        b = 1LL*b*b%MOD;
        p >>= 1;
    }
    return ret;
}
inline void fwt(int A[], int n, int type) {
    for(rg int s=2;s<=n;s<<=1)
        for(rg int i=0,t=(s>>1);i<n;i+=s)
            for(rg int j=0;j<t;j++) {
                int x = A[i+j], y = A[i+j+t];
                A[i+j] = x, A[i+j+t] = (y + type*x)%MOD;
            }
}
int p[MAXN][1<<MAXN], q[MAXN][1<<MAXN], r[MAXN][1<<MAXN];
int f[1<<MAXN];
int main() {
    int n; scanf("%d", &n);
    for(rg int i=0;i<n;i++)
        for(rg int j=i+1;j<n;j++)
            scanf("%d", &C[i][j]), C[j][i] = C[i][j];
    for(rg int i=0;i<n;i++)
        lg[1<<i] = i;
    int t = (1<<n); f[0] = 1;
    for(rg int s=1;s<t;s++) {
        int x = lg[lowbit(s)]; f[s] = f[s^(1<<x)];
        for(rg int j=0;j<n;j++)
            if( (1<<j) & (s^(1<<x)) ) f[s] = 1LL*f[s]*(C[x][j] + 1)%MOD;
    }
    int t1 = (t>>1);
    for(rg int s=1;s<t1;s++)
        bits[s] = bits[s>>1] + (s & 1);
    for(rg int s=0;s<t1;s++) {
        p[bits[s]][s] = f[s<<1];
        q[bits[s]][s] = f[s<<1|1];
    }
    for(rg int i=0;i<n;i++)
        fwt(p[i], t1, 1), fwt(q[i], t1, 1);
    for(rg int s=0;s<t1;s++) {
        int inv = pow_mod(p[0][s], MOD-2);
        for(rg int i=0;i<n;i++) {
            r[i][s] = q[i][s];
            for(rg int j=0;j<i;j++)
                r[i][s] = (r[i][s] - 1LL*r[j][s]*p[i-j][s]%MOD)%MOD;
            r[i][s] = 1LL*r[i][s]*inv%MOD;
        }
    }
    fwt(r[n-1], t1, -1);
    printf("%d\n", (r[n-1][t1-1] + MOD)%MOD);
}

@details@

虽然标程给出的是通过 FMT 来做这道题,但是可以看出完全没有这个必要。
另外,本题 3s 时限是不是真的太卡了点……

转载于:https://www.cnblogs.com/Tiw-Air-OAO/p/10274529.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值