矩阵树定理+二项式反演复习
矩阵树定理+二项式反演复习
= 计数题复习
二项式反演
二项式定理的至多和恰好的转化:
\[f_n = \sum_{i=0}^n {n\choose i} g_i \Leftrightarrow g_n=\sum_{i=0}^n (-1)^{n-i}{n\choose i} f_i
\]
至少和恰好的转化:
\[f_n= \sum_{i=n}^m {i\choose n}g_i\Leftrightarrow g_n=\sum_{i=n}^m(-1)^{i-n}{i\choose n} f_i
\]
这两个是比较有用的,一般看到“恰好"就要直接想到二项式反演。
然后min-max容斥也可以通过二项式反演直接推出来
\[max(S)=\sum_{T\subset S}(-1)^{|T|-1}min(T)
\]
BZOJ2839
裸题,之前学的时候没做
恰好转化成至少就好了
PKUWC2018 随机游走
考虑minmax容斥之后怎么做,有dp式子:
\[f(S,i)=[i\not\in S]\left(\sum_v \frac{f(S,v)}{deg_i}+1\right)
\]
高消好像有点慢。下面是抄题解时间。(大师,我悟了!)
设\(f[i]=A[i]*f[fa]+B[i]\) ,尝试推出f的递推式:
\[\begin{aligned}
f[i] &=\frac{1}{\operatorname{deg}[i]}\left(f[f a]+\sum_{j \in s o n_{i}} f[j]\right)+1 \\
f[i] &=\frac{1}{\operatorname{deg}[i]}\left(f[f a]+\sum_{j \in s o n_{i}}(A[j] \times f[i]+B[j])\right)+1 \\
\left(1-\frac{\sum_{j \in s o n_{i}} A[j]}{\operatorname{deg}_{i}}\right) f[i] &=\frac{1}{\operatorname{deg}[i]} f[f a]+\frac{1}{\operatorname{deg}[i]} \sum_{j \in s o n_{i}} B[j]+1 \\
\left(\operatorname{deg}[i]-\sum_{j \in s o n_{i}} A[j]\right) f[i] &=f[f a]+\sum_{j \in s o n_{i}} B[j]+\operatorname{deg}[i] \\
f[i] &=\frac{1}{\left(\operatorname{deg}[i]-\sum_{j \in \operatorname{son}_{i}} A[j]\right)} f[f a]+\frac{\sum_{j \in \operatorname{son}_{i}} B[j]+\operatorname{deg}[i]}{\left(\operatorname{deg}[i]-\sum_{j \in \operatorname{son}_{i}} A[j]\right)}
\end{aligned}
\]
那么就可以\(n2^n\)递推了,最后FWT搞出高维前缀和。(minmax容斥多次询问一般配合高维前缀和食用QwQ)总复杂度\(O(n2^nlogw+nq)\)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define ll long long
#define FOR(i,a,b) for(register int i=a;i<=b;++i)
#define ROF(i,a,b) for(register int i=a;i>=b;--i)
const int mod = 998244353;
#define chk(a,b) a=(a+b)>=mod?a+b-mod:a+b;
using namespace std;
int read(){
int x=0,pos=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') pos=0;
for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
return pos?x:-x;
}
const int N = 1<<19;
struct node{
int v,nex;
}edge[100];
int head[1001],top=0;
void add(int u,int v){
edge[++top].v=v;edge[top].nex=head[u];head[u]=top;
}
int n,q,x;
int ksm(int a,int b){int res=1;while(b){if(b&1) res=1ll*res*a%mod;a=1ll*a*a%mod,b>>=1;}return res;}
int f[N],a[20],b[20];
short sum[N];
void dfs(int now,int fa,int S){
if((1<<(now-1))&S) return;
int dg=0;
for(int i=head[now];i;i=edge[i].nex){
int v=edge[i].v;dg++;
if(v==fa) continue;
dfs(v,now,S);
(a[now]+=a[v])%=mod,(b[now]+=b[v])%=mod;
}
a[now]=(dg-a[now]+mod)%mod;b[now]=(b[now]+dg)%mod;
a[now]=ksm(a[now],mod-2);b[now]=1ll*b[now]*a[now]%mod;
}
void fwt(int *f){
for(int l=2;l<=(1<<n);l*=2){ int m=l/2;
for(int *g=f;g!=f+(1<<n);g+=l){
for(int j=0;j<m;j++){
g[j+m]=(g[j+m]+g[j])%mod;
}
}
}
}
int main(){
n=read(),q=read(),x=read();
for(int i=1;i<n;i++){
int u=read(),v=read();
add(u,v);add(v,u);
}
for(int i=1;i<(1<<n);i++){
memset(a,0,sizeof a),memset(b,0,sizeof b);
dfs(x,0,i);
sum[i]=sum[i>>1]+(i&1);
if(!(sum[i]&1)) f[i]=(mod-b[x])%mod;else f[i]=b[x];
}
fwt(f);
for(int i=1;i<=q;i++){
int k=read(),now=0;
for(int j=1;j<=k;j++){
int u=read();now|=(1<<(u-1));
}
printf("%d\n",f[now]);
}
return 0;
}
SDOI2014 重建
\[\begin{align}ans &= \sum_{T\subset S} \prod_{e\in T} P_e\prod_{e\not \in T}(1-P_e)\\&= \sum_{T\subset S}\prod_{e\in T} P_e\frac{\prod _e (1-P_e)}{\prod_{e\in T}(1-P_e)}\\&=\prod_{e} (1-P_e)\sum_{T\subset S}\prod _{e\in T}\frac{P_e}{1-P_e}\end{align}
\]
然后有个东西叫做变元矩阵树定理(第一次听说
矩阵树定理求的是\(\sum_{T\subset S}\prod_{e\in T} 1\),考虑矩阵的构造:
\[\boldsymbol{L}_{i, j}=\left\{\begin{array}{l}\sum_{j=1}^{|V|}[(i, j) \in E], i=j(i \text {的度数}) \\-\sum_{e \in E}[(i, j)=e], i \neq j(\text {边} i, j \text {的个数})\end{array}\right.
\]
把\(L_{i,i}\)换成与i相邻的边的权值和,\(L_{i,j}\)换成ij权值的负值,跟矩阵树定理差不多,就可以直接做了
#include<bits/stdc++.h>
#define db long double
using namespace std;
int read(){
int x=0,pos=1;char ch=getchar();for(;!isdigit(ch);ch=getchar()) if(ch=='-') pos=0;
for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';return pos?x:-x;
}
#define FOR(i,a,b) for(int i=a;i<=b;++i)
const int N = 201;
#define ROF(i,a,b) for(int i=b;i>=a;--i)
int n;
db g[N][N],f[N][N];
const db eps = 1e-7;
void gauss(){
for(int i=1;i<=n;i++){
int x=i;
FOR(j,i+1,n){
if(fabs(f[x][i])<fabs(f[j][i])) x=j;
}
swap(f[x],f[i]);
FOR(j,i+1,n){
db d=f[j][i]/f[i][i];
ROF(k,i,n){
f[j][k]-=d*f[i][k];
}
}
}
}
int main(){
n=read();
db ans=1;
FOR(i,1,n){
FOR(j,1,n){
scanf("%Lf",&g[i][j]);
if(g[i][j]==0) g[i][j]=eps;
if(g[i][j]==1) g[i][j]-=eps;
if(i<j) ans*=(1-g[i][j]);
}
}
FOR(i,1,n){
FOR(j,1,n){
if(i==j){
FOR(l,1,n){
if(i!=l) f[i][i]+=g[i][l]/(1-g[i][l]);
}
}else{
f[i][j]=-g[i][j]/(1-g[i][j]);
}
}
}
--n;
gauss();
for(int i=1;i<=n;i++){
ans*=f[i][i];
}
printf("%.20Lf",ans);
return 0;
}
普通的矩阵树定理(标题:复习)
f(i,j) = i和j是否有边*-1
f(i,i) = i的度数
然后去掉一行一列(一般去最后),求det就是答案(消成上三角对角线乘起来)
就先这么多吧
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步