= 计数题复习
二项式定理的至多和恰好的转化:
至少和恰好的转化:
这两个是比较有用的,一般看到“恰好"就要直接想到二项式反演。
然后min-max容斥也可以通过二项式反演直接推出来
裸题,之前学的时候没做
恰好转化成至少就好了
考虑minmax容斥之后怎么做,有dp式子:
高消好像有点慢。下面是抄题解时间。(大师,我悟了!)
设\(f[i]=A[i]*f[fa]+B[i]\) ,尝试推出f的递推式:
那么就可以\(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;
}
然后有个东西叫做变元矩阵树定理(第一次听说
矩阵树定理求的是\(\sum_{T\subset S}\prod_{e\in T} 1\),考虑矩阵的构造:
把\(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就是答案(消成上三角对角线乘起来)
就先这么多吧
原文:https://www.cnblogs.com/lcyfrog/p/12903195.html