• @雅礼集训01/13



    @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_{tsubset s}f(t)*g(s-t) ]

    其中为了避免重复计算,我们还要求 s 中的编号最小的点与 t 中的编号最小的点相同。
    既然是容斥,放宽了限制,那么 g 也很好求:

    [g(s)=prod_{i<j且i,jin 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_{tsubset s}f'(t)*g'(s-t) ]

    注意 h 和 g' 是不一样的,前者必须要包含 1 号点,后者不能包含一号点。

    长得一幅子集卷积模样。我们用卷积的形式再来写一遍式子:

    [f' = h - f'*g' ]

    基本的代数变形:

    [f' = h*(g'+1)^{-1} ]

    那么我们就是要来求解子集卷积的逆运算。

    @part - 3@

    怎么求解子集卷积的逆运算呢?我们先来看看子集卷积本身:

    [f(s)=sum_{tsubset 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
    ", (r[n-1][t1-1] + MOD)%MOD);
    }
    

    @details@

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

  • 相关阅读:
    推荐一款idea 翻译插件 ECTranslation
    idea 执行maven 命令
    Future Clalback使用案例
    newCachedThreadPool使用案例
    线程池 原理学习笔记
    使用curator框架简单操作zookeeper 学习笔记
    mongo aggregate 用法记录
    ReentrantLock 学习笔记
    VUE:过渡&动画
    VUE:生命周期
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/10274529.html
Copyright © 2020-2023  润新知