矩阵树定理+二项式反演复习

矩阵树定理+二项式反演复习

= 计数题复习

二项式反演

二项式定理的至多和恰好的转化:

\[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就是答案(消成上三角对角线乘起来)

就先这么多吧

猜你喜欢

转载自www.cnblogs.com/lcyfrog/p/12903195.html