Fast as Fast as Ryser 简单题解
集合幂级数我们喜欢你!
沃尔什变换我们喜欢你!
子集卷积我们喜欢你!
多项式 exp 我们喜欢你!
扩展模数域我们喜欢你!
逐点牛迭代我们喜欢你!
转置原理我们喜欢你!
事实是上面这些算法我一个都不会,集合幂级数那套还是太难了 o(╥﹏╥)o
给定一个完全无向图 \(G\),每条边有边权。保证 \(w_{u,v}=w_{v,u},w_{u,u}=0\)。
定义一组匹配的权值为对应边权的乘积。\(\forall k\in \left[0,\left\lfloor\frac{n}{2}\right\rfloor\right]\),求所有大小为 \(k\) 的匹配的权值和,对 \(\color{red}{2^{64}}\) 取模。
\(1\le n\le 40,w_{u,v}\in [0,2^{64}-1]\)
首先有个非常巧妙的转化。
如果 \(n\) 为奇数,那么额外加一个点,然后所有下标 \(0-\text{index}\)
考虑在 \(2i\) 和 \(2i+1\) 之间连一条虚边,称它们两个为第 \(i\) 段。
最后连上匹配上了的边和所有虚边的图会由 若干条链 和 若干个环 构成。
注意到匹配数等于 \(\dfrac{n}{2}\) 减去链的条数,并且最后每个点都会用上。接下来令 \(N=n/2\)。
这东西能对环和链列集合幂级数,然后算算算。但是由于上述原因,我们考虑设计 dp。
我们钦定一个拓展方式,然后拓展到 \(x\) 的下一步强制拓展到 \(x\text{ xor }1\),以此保证虚边必选。
此时我们只需记录扩展了哪些段,那么集合大小就被我们缩小到了 \(2^N\)。
最终要求拓展到全集 \(U\),有 \(k\) 条链的权值和。
比如当前的环和链拓展了 \(S\) 内的点,为了避免算重复,我们钦定下一个要扩展 \(p\)。
- 其中 \(p\) 为 最小的不在 \(S\) 中的点,即 \(p={\text{lowbit}}(U\,\backslash\,S)\),并且 \(2\mid p\)。
然后环和链的顺序可能会算重。
环是好做的,我们钦定以 \(p\to \cdots\to p+1\to p\) 这个顺序贡献即可。
链考虑比如说 \(x\to \cdots\to p\to p+1\to \cdots \to y\)。
把 \(p\) 看做起点,\(p+1\) 看做终点。我们先从 \(p\) 扩展,某个时刻再从 \(p+1\) 开始扩展。
比如先做 \(p\to x\) 的拓展,由于此时 \(p\) 仍然满足 \(p={\text{lowbit}}(U\,\backslash\,S)\),直接跳到 \(p+1\),继续扩展。
于是你就可以列 dp 了,下面用到了分阶段 dp 的思想,这一般也就是应用在 转移一条链或者环 的题。
我们设计一个 \(g_{i,S}\) 就表示当前扩展了 \(i\) 条链,扩展了集合 \(S\)。
从 \(0\to N\) 枚举 \(i\),再从小到大枚举 \(S\)。
注意到 \(0\sim i-1\) 一定被包含在 \(S\) 中,所以可能的 \(S\) 只有 \(2^{N-i}\) 种!
枚举完 \(S\),我们确定了 \(p\)。考虑怎么把 \(g_{i,S}\) 转移出去。
接下来要分阶段 dp,当前要处理扩展 \(p\) 这一个阶段。
就是 $g\to $ 从 \(p\) 开始的 初始情况,设作 \(f\) 数组。
然后 \(f\) 内部转移一条链或者环,转移完从 \(p\) 开始的扩展。
最终再 \(f\to g\) 贡献回来。
因为如果只在一个数组转移,不太好做到转移链或环这样的结构,用两个数组互相转移就能有效满足这点。
定义 \(f_{S,x,0/1}\) 表示当前集合为 \(S\),拓展到点 \(x\),当前是从 起点还是终点 开始拓展。
- 注意这里 \(f\) 的 \(S\) 还是始终不包含 \(p\),在最后贡献的时候才包含 \(p\) 回去!
对于 \((i,S)\) 时刻,\(p={\text{lowbit}}(U\,\backslash\,S)\)。
初始先接收 \(p\) 阶段的初值:
然后考虑 \(p\) 阶段,枚举当前扩展到 \(x\ge p\)。
首先 \(f_{S,x,0/1}\to g\):
然后枚举 \(x\) 的下一步 \(y\ge p+2\),并且 \(y\) 所在的段 \(y/2\not\in S\)。
从 \(x\to y\),嗯事实上是到 \(y\text{ xor }1\),因为最初钦定了走到某个点必须走其段内的另一个点。
最终记得清空 \(p\) 阶段的 \(f\)。
最终 \(k\) 的答案就是 \(g_{N-k,U}\),直接输出即可。
复杂度 \(\sum\limits_{i=0}^{N} 2^{N-i}(N-i)^2=\mathcal{O}(N^22^N)\),足以通过。
代码很短,思路看代码也很好理解。
#include<bits/stdc++.h>
#define u64 unsigned long long
#define ctz __builtin_ctz
#define fr(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);
using namespace std;
const int N=43,M=1<<20|3;
int n;u64 w[N][N],f[M][N][2],g[N/2][M];
int main()
{
cin.tie(0)->sync_with_stdio(0);cout.tie(0);cin>>n;
for(int i=0;i<n;i++) for(int j=0;j<n;j++) cin>>w[i][j];
int _n=n/2;n=(n+1)/2;g[0][0]=1;
for(int i=0;i<=n;i++) for(int j=0;j+1<1<<(n-i);j++)
{
int k=j<<i|((1<<i)-1),p=ctz(~k),P=p<<1;f[k][P][0]=g[i][k];
for(int x=P;x<2*n;x++) f[k][P|1][1]+=f[k][x][0];// p 阶段初始
for(int x=P;x<2*n;x++)
{
g[i][k|1<<p]+=f[k][x][0]*w[x][P|1];
g[i+1][k|1<<p]+=f[k][x][1];//f->g
for(int y=P+2;y<2*n;y++) if(k>>(y/2)&1^1)
f[k|1<<(y/2)][y^1][0]+=f[k][x][0]*w[x][y],
f[k|1<<(y/2)][y^1][1]+=f[k][x][1]*w[x][y];//f 内部
f[k][x][0]=f[k][x][1]=0;//清空
}
}
for(int i=0;i<=_n;i++) cout<<g[n-i][(1<<n)-1]<<" ";//答案
return 0;
}

浙公网安备 33010602011771号