矩阵树定理+二项式反演复习
= 计数题复习
二项式反演
二项式定理的至多和恰好的转化:
[f_n = sum_{i=0}^n {nchoose i} g_i Leftrightarrow g_n=sum_{i=0}^n (-1)^{n-i}{nchoose i} f_i
]
至少和恰好的转化:
[f_n= sum_{i=n}^m {ichoose n}g_iLeftrightarrow g_n=sum_{i=n}^m(-1)^{i-n}{ichoose n} f_i
]
这两个是比较有用的,一般看到“恰好"就要直接想到二项式反演。
然后min-max容斥也可以通过二项式反演直接推出来
[max(S)=sum_{Tsubset S}(-1)^{|T|-1}min(T)
]
BZOJ2839
裸题,之前学的时候没做
恰好转化成至少就好了
PKUWC2018 随机游走
考虑minmax容斥之后怎么做,有dp式子:
[f(S,i)=[i
otin S]left(sum_v frac{f(S,v)}{deg_i}+1
ight)
]
高消好像有点慢。下面是抄题解时间。(大师,我悟了!)
设(f[i]=A[i]*f[fa]+B[i]) ,尝试推出f的递推式:
[egin{aligned}
f[i] &=frac{1}{operatorname{deg}[i]}left(f[f a]+sum_{j in s o n_{i}} f[j]
ight)+1 \
f[i] &=frac{1}{operatorname{deg}[i]}left(f[f a]+sum_{j in s o n_{i}}(A[j] imes f[i]+B[j])
ight)+1 \
left(1-frac{sum_{j in s o n_{i}} A[j]}{operatorname{deg}_{i}}
ight) 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]
ight) 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]
ight)} 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]
ight)}
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
",f[now]);
}
return 0;
}
SDOI2014 重建
[egin{align}ans &= sum_{Tsubset S} prod_{ein T} P_eprod_{e
ot in T}(1-P_e)\&= sum_{Tsubset S}prod_{ein T} P_efrac{prod _e (1-P_e)}{prod_{ein T}(1-P_e)}\&=prod_{e} (1-P_e)sum_{Tsubset S}prod _{ein T}frac{P_e}{1-P_e}end{align}
]
然后有个东西叫做变元矩阵树定理(第一次听说
矩阵树定理求的是(sum_{Tsubset S}prod_{ein T} 1),考虑矩阵的构造:
[oldsymbol{L}_{i, j}=left{egin{array}{l}sum_{j=1}^{|V|}[(i, j) in E], i=j(i ext {的度数}) \-sum_{e in E}[(i, j)=e], i
eq j( ext {边} i, j ext {的个数})end{array}
ight.
]
把(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就是答案(消成上三角对角线乘起来)
就先这么多吧