计数好题

题意:给定简单无向图 \(G=(V,E),|V|=n,|E|=m\),有 \(n\leq 16,m\leq {n\choose 2}\),求所有为基环树的子图的权值之和。一个基环树的权值定义为 \(2^w\),其中 \(w\) 是非叶子节点的个数。

这篇博客提到的最小元状压 DP 的技巧,我们可以轻松地在 \(O(2^n n^2)\) 的时间内求出一个点子集 \(S\) 成为环的方案数 \(cyc_S\)。由于我们钦定最小元是环起点,两个方向分别被 DP 了一次,所以答案记得 \(\div 2\)。需要注意仅有两个点的不算环!

我们现在先考虑不带权值计算一个点子集成为基环树的方案数,设其为 \(dp_S\)。一个暴力的想法是枚举 \(T\subset S\) 将 \(T\) 作为环缩成一个点跑矩阵树,复杂度 \(O(3^n n^3)\),可以拿 30 分。

我们得到了 \(dp_S\) 如何计算带权值的方案呢?由于权值跟叶子数量有关,一个经典套路就是钦定一些叶子上容斥。钦定 \(T\subset S\) 必须当叶子,\(S/T\) 部分随便取一颗基环树,那么 \(S/T,T\) 间连边方案数就是 \(T\) 中的点在 \(S/T\) 中的度数乘积,不妨记为 \(\text{cross}(T,S/T)=\),还有对叶子的 \((-1)^{|T|}\) 的容斥系数。那么有:

\[\begin{aligned}
res&=\sum_{S\subset V} \sum_{T\subset S} dp_{S/T} 2^{|S/T|} (-1)^{|T|} \text{cross}(T,S/T)\\
&=\sum_{S\subset V} \sum_{T\subset S} dp_{S/T} 2^{|S/T|} \prod_{x\in T} -\text{cross}(x,S/T)\\
&=\sum_{P\subset V} \sum_{S\supset P} dp_{P} 2^{P} \prod_{x\in S/P} -\text{cross}(x,P)\\
&=\sum_{P\subset V} dp_{P} 2^{P} \prod_{x\in V/P} 1-\text{cross}(x,P)\\
\end{aligned}
\]

如果能更快地求 \(dp_{P}\),那么后面的部分就可以在 \(O(2^n n)\) 的时间内解决。

接下来有两种方式求 \(dp\) 数组。

继续对叶子容斥

就是UOJ 官解算法五虞皓翔做法

我们不妨设 \(f_{S,T}\) 表示点集为 \(S\),叶子点集为 \(T\) 的基环树方案数。然后依然考虑钦定部分叶子。

设 \(F_{S,T}=\sum_{T'\supset T} f_{S,T'}\),相当于算钦定 \(T\) 集合的方案数,\(\text{IFWT}\)(高维差分)回去就是 \(f_{S,T}=\sum_{T'\supset T} F_{S,T'}(-1)^{|T'/T|}\)。

注意到 \(dp_S=\sum_{T\subset S} f_{S,T}=F_{S,\emptyset}\),以及 \(cyc_S=f_{S,\emptyset}=\sum_{T\subset S} F_{S,T}(-1)^{|T|}\)。只需要算出所有 \(T\) 非空的 \(F_{S,T}\) 就可以算出 \(dp_{S}\)。

而 \(T\) 若非空,我们根据容斥的组合意义容易导出:\(F_{S,T}=\text{cross}(T,S/T) dp_{S/T}\)。

带入上述式子解出 \(dp_S\),我们有:

\[dp_S=cyc_S-\sum_{T\subset S,T\neq \emptyset} \text{cross}(T,S/T) dp_{S/T} (-1)^{|T|}
\]

可以在 \(O(3^n)\) 的时间内愉快地计算。

注意为了递推 \(\text{cross}\) 函数,我们需要枚举 \(S/T\) 然后刷表。

#include <cstdio>
#include <vector>
using namespace std;
int read(){
char c=getchar();int x=0;
while(c<48||c>57) c=getchar();
do x=(x<<1)+(x<<3)+(c^48),c=getchar();
while(c>=48&&c<=57);
return x;
}
const int P=998244353;
typedef long long ll;
int path[1<<16][16];
int cyc[1<<16],adj[16];
int n,m,msk;
int pw[17],coe[1<<16],sum[1<<16],f[16],res;
void inc(int &x,int v){if((x+=v)>=P) x-=P;}
void dec(int &x,int v){if((x-=v)<0) x+=P;}
int stk[1<<16],tp;
int main(){
n=read();m=read();msk=(1<<n)-1;
for(int i=0;i<m;++i){
int u=read()-1,v=read()-1;
adj[u]|=(1<<v);
adj[v]|=(1<<u);
}
for(int i=0;i<n;++i) path[1<<i][i]=1;
pw[0]=1;
for(int i=1;i<=n;++i) inc(pw[i]=pw[i-1],pw[i-1]);
for(int s=1;s<(1<<n);++s){
int lb=__builtin_ctz(s);
for(int i=lb;i<n;++i){
if(!path[s][i]) continue;
for(int j=lb+1;j<n;++j){
if(s>>j&1) continue;
if(adj[i]>>j&1) inc(path[s|(1<<j)][j],path[s][i]);
}
}
}
for(int s=1;s<(1<<n);++s){
int lb=__builtin_ctz(s);
for(int i=lb+1;i<n;++i)
if(adj[i]>>lb&1) inc(cyc[s],path[s][i]);
if(cyc[s]&1) cyc[s]+=P;
cyc[s]>>=1;
if(__builtin_popcount(s)<=2u) cyc[s]=0;
}
sum[0]=1;
for(int s=0;s<(1<<n);++s){
if(!cyc[s]) continue;
int tt=(ll)pw[__builtin_popcount(s)]*cyc[s]%P;
for(int i=0;i<n;++i)
if(~s>>i&1)
tt=(ll)(P+1-(f[i]=__builtin_popcount(adj[i]&s)))*tt%P;
for(int dlt=msk^s;dlt;dlt=(dlt-1)&(msk^s)) stk[tp++]=dlt;
while(tp){
int dlt=stk[--tp],lb=__builtin_ctz(dlt);
sum[dlt]=(ll)sum[dlt^(1<<lb)]*f[lb]%P;
if(__builtin_parity(dlt)) inc(cyc[s|dlt],(ll)cyc[s]*sum[dlt]%P);
else dec(cyc[s|dlt],(ll)cyc[s]*sum[dlt]%P);
}
inc(res,tt);
}
printf("%d\n",res);
return 0;
}

给无向图定向

zx2003 的做法

众所周知内向基环树就是每个点出度都为 \(1\) 的弱联通图。计算无向图基环树我们有个方法:给无向图每一个点选择一条邻边定向为出边,考虑所有有向边形成了内向基环森林。

那么我们只需要对于每一个点子集导出子图计算度数的乘积,然后对集合幂级数求个 \(\ln\) 就可以得到一颗内向基环树。

有个小瑕疵,环长为 \(2\) 的内向基环树不能算在答案里。然而当我们把长度为 \(2\) 的环看成一条边,那么相当于在一个生成树上选一条边的方案数。用矩阵树定理计算即可。

时间复杂度为 \(O(2^nn^3)\),瓶颈在对每个点子集计算矩阵树定理,感觉可能还能优化。

#include <cstdio>
#include <vector>
#include <algorithm>
#pragma GCC optimize(2,3,"Ofast")
#define FWTfor \
for(int i=1;i<(1<<n);i<<=1) \
for(int j=0;j<(1<<n);j+=(i<<1)) \
for(int k=j;k<(j|i);++k)
using namespace std;
int read(){
char c=getchar();int x=0;
while(c<48||c>57) c=getchar();
do x=(x<<1)+(x<<3)+(c^48),c=getchar();
while(c>=48&&c<=57);
return x;
}
const int P=998244353;
typedef long long ll;
int path[1<<16][16];
int cyc[1<<16],adj[16],pw[17],inv[17];
int n,m,msk;
void inc(int &x,int v){if((x+=v)>=P) x-=P;}
void dec(int &x,int v){if((x-=v)<0) x+=P;}
int f[17][1<<16],g[17][1<<16];
int ans[1<<16],res;
void FWT(int *arr){FWTfor inc(arr[k|i],arr[k]);}
void IFWT(int *arr){FWTfor dec(arr[k|i],arr[k]);}
int mat[17][17],len;
int qp(int a,int b=P-2){
int res=1;
while(b){
if(b&1) res=(ll)res*a%P;
b>>=1;a=(ll)a*a%P;
}
return res;
}
int det(){
bool fl=0;
int res=1;
for(int i=1;i<=len;++i){
int p=i;
while(p<=len&&!mat[p][i]) ++p;
if(p>len) return 0;
if(p>i){
for(int j=i;j<=len;++j) swap(mat[p][j],mat[i][j]);
fl^=1;
}
res=(ll)res*mat[p][i]%P;
int iv=qp(mat[p][i]);
for(int j=i;j<=len;++j) mat[i][j]=(ll)mat[i][j]*iv%P;
for(int j=i+1;j<=len;++j)
for(int k=len;k>=i;--k)
dec(mat[j][k],(ll)mat[j][i]*mat[i][k]%P);
}
if(fl) return P-res;
return res;
}
int calc(int s){
len=0;
for(int i=0;i<n;++i)
if(s>>i&1){
++len;
mat[len][len]=0;
for(int j=0,t=0;j<n;++j)
if(s>>j&1){
++t;
if(t!=len) mat[len][t]=0;
if(adj[i]>>j&1){++mat[len][len];--mat[len][t];}
}
}
for(int i=1;i<=len;++i)
for(int j=1;j<=len;++j)
if(mat[i][j]<0) mat[i][j]+=P;
--len;
return det();
}
int main(){
n=read();m=read();msk=(1<<n)-1;
for(int i=0;i<m;++i){
int u=read()-1,v=read()-1;
adj[u]|=(1<<v);
adj[v]|=(1<<u);
}
for(int i=0;i<n;++i) path[1<<i][i]=1;
inv[1]=pw[0]=1;
for(int i=1;i<=n;++i) inc(pw[i]=pw[i-1],pw[i-1]);
for(int i=2;i<=n;++i) inv[i]=(ll)inv[P%i]*(P-P/i)%P;
for(int s=1;s<(1<<n);++s){
int lb=__builtin_ctz(s);
for(int i=lb;i<n;++i){
if(!path[s][i]) continue;
for(int j=lb+1;j<n;++j){
if(s>>j&1) continue;
if(adj[i]>>j&1) inc(path[s|(1<<j)][j],path[s][i]);
}
}
}
f[0][0]=1;
for(int s=1;s<(1<<n);++s){
int lb=__builtin_ctz(s),sz=__builtin_popcount(s);
f[sz][s]=1;
for(int i=0;i<n;++i)
if(s>>i&1) f[sz][s]=(ll)__builtin_popcount(adj[i]&s)*f[sz][s]%P;
for(int i=lb+1;i<n;++i)
if(adj[i]>>lb&1) inc(cyc[s],path[s][i]);
if(cyc[s]&1) cyc[s]+=P;
cyc[s]>>=1;
if(sz<=2) cyc[s]=0;
}
for(int i=0;i<=n;++i) FWT(f[i]);
for(int i=1;i<=n;++i){
for(int s=0;s<(1<<n);++s) g[i][s]=f[i][s];
for(int s=0;s<(1<<n);++s){
int tmp=0;
for(int j=1;j<i;++j)
inc(tmp,(ll)j*g[j][s]%P*f[i-j][s]%P);
dec(g[i][s],(ll)inv[i]*tmp%P);
}
}
for(int i=0;i<=n;++i) IFWT(g[i]);
for(int s=1;s<(1<<n);++s){
int sz=__builtin_popcount(s);
int cur=g[sz][s];
dec(cur,(ll)calc(s)*(sz-1)%P);
cur=(ll)cur*pw[sz]%P;
if(cur&1) cur+=P;
cur>>=1;
for(int i=0;i<n;++i)
if(~s>>i&1) cur=(ll)cur*(P+1-__builtin_popcount(adj[i]&s))%P;
inc(res,cur);
}
putchar('\n');
printf("%d\n",res);
return 0;
}

邪典做法

如果我们连 \(n\) 个点 \(n\) 条边的联通图是基环树都不知道,但是我们及其擅长集合幂级数与多项式。

那么我们就可以对于点集和边数设二元集合幂级数 \(F(x,y)=\sum f_{S,e} x^S y^e\),对二元的占位幂级数求 \(\ln\) 就可以保证联通。

复杂度 \(O(2^nn^4)\),不知是否可行,也不知能否通过。

[UR #14]人类补完计划的更多相关文章

  1. [TaskList] 省选前板子补完计划

    省选前本子补完计划 [ ] 带权并查集 [ ] 树上莫队 - UOJ58 [WC2013]糖果公园 loj2485「CEOI2017」Chase

  2. QBXT 2017GoKing problems 补完计划

    10.11 Updata : 烦死了...麻烦死了...不补了..就这些吧 20171001 上: 100 + 90 + 90 = 280 = rank 8 T1 /* T1 从最大的数开始倒着枚举 ...

  3. bzoj Usaco补完计划(优先级 Gold>Silver>资格赛)

    听说KPM初二暑假就补完了啊%%% 先刷Gold再刷Silver(因为目测没那么多时间刷Silver,方便以后TJ2333(雾 按AC数降序刷 ---------------------------- ...

  4. NodeJS学习:爬虫小探补完计划

    说明:本文在个人博客地址为edwardesire.com,欢迎前来品尝. 书接上回,我们需要修改程序以达到连续抓取40个页面的内容.也就是说我们需要输出每篇文章的标题.链接.第一条评论.评论用户和论坛 ...

  5. CodeVS1169 传纸条 [DP补完计划]

    题目传送门 题目描述 Description 小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题.一次素质拓展活动中,班上同学安排做成一个m行n列的矩阵,而小渊和小轩被安排在矩阵对角线的两端, ...

  6. NLP 开源形近字算法补完计划(完结篇)

    前言 所有的故事都有开始,也终将结束. 本文将作为 NLP 汉字相似度的完结篇,为该系列画上一个句号. 起-NLP 中文形近字相似度计算思路 承-中文形近字相似度算法实现,为汉字 NLP 尽一点绵薄之 ...

  7. 洛谷P2224 [HNOI2001] 产品加工 [DP补完计划,背包]

    题目传送门 产品加工 题目描述 某加工厂有A.B两台机器,来加工的产品可以由其中任何一台机器完成,或者两台机器共同完成.由于受到机器性能和产品特性的限制,不同的机器加工同一产品所需的时间会不同,若同时 ...

  8. POJ1742 Coin [DP补完计划]

    题目传送门 Coins Time Limit: 3000MS   Memory Limit: 30000K Total Submissions: 41707   Accepted: 14125 Des ...

  9. 洛谷P1280 尼克的任务 [DP补完计划]

    题目传送门 题目描述 尼克每天上班之前都连接上英特网,接收他的上司发来的邮件,这些邮件包含了尼克主管的部门当天要完成的全部任务,每个任务由一个开始时刻与一个持续时间构成. 尼克的一个工作日为N分钟,从 ...

  10. PHP函数补完:stream_context_create()模拟POST/GET

    PHP函数补完:stream_context_create()模拟POST/GET PHP流的创建 在 2011年01月08日 那天写的     已经有 9408 次阅读了 感谢 参考或原文   服务 ...

随机推荐

  1. ACE Editor 常用Api(转)

    ACE 是一个开源的.独立的.基于浏览器的代码编辑器,可以嵌入到任何web页面或JavaScript应用程序中.ACE支持超过60种语言语法高亮,并能够处理代码多达400万行的大型文档.ACE开发团队 ...

  2. userdel: user zhangsan is currently used by process 1057

    我个人推测是在root用户下su 切换到xiaoming用户,然后在xiaoming用户下又切换回root,但是xiaoming用户还被某个进程占用着,所以进程不死,用户del不掉. 所以我们在命令行 ...

  3. excel里面嵌入一个表格

    excel里怎么嵌入表格 excel是我们工作中经常会用的软件,有时两表格想放在一起比较,但是行高列宽调起来顾此失彼,so: 软件版本:Microsoft Office Excel 2010 操作系统 ...

  4. vulnhub靶场之PYLINGTON: 1

    准备: 攻击机:虚拟机kali.本机win10. 靶机:Pylington: 1,下载地址:https://download.vulnhub.com/pylington/pylington.ova,下 ...

  5. linux 镜像备份

    linux 镜像备份 使用linux虚拟机的方法 优点 镜像大小比较小 缺点 速度可能比较慢 方法 1.打开虚拟机 我用的ubuntu,读卡器连接电脑虚拟机,ubuntu一般会自动挂载 df -h # ...

  6. 一文带你吃透Redis

    目录 1. 基本数据结构 2. 数据持久化 3. 高可用 4. 缓存 文章字数大约1.9万字,阅读大概需要66分钟,建议收藏后慢慢阅读!!! 1. 基本数据结构 什么是Redis Redis是一个数据 ...

  7. 依图在实时音视频中语音处理的挑战丨RTC Dev Meetup

    前言 「语音处理」是实时互动领域中非常重要的一个场景,在声网发起的「RTC Dev Meetup丨语音处理在实时互动领域的技术实践和应用」 活动中,来自百度.寰宇科技和依图的技术专家,围绕该话题进行了 ...

  8. golang中关于死锁的思考与学习

    1.Golang中死锁的触发条件 1.1 书上关于死锁的四个必要条件的讲解 发生死锁时,线程永远不能完成,系统资源被阻碍使用,以致于阻止了其他作业开始执行.在讨论处理死锁问题的各种方法之前,我们首先深 ...

  9. 【RSocket】使用 RSocket(三)——服务端主动调用客户端方法

    1. 编写客户端接收请求的逻辑 我们可以在初始化 Rsocket 实例的时候指定客户端可以被调用的方法,使用 acceptor() 指定可被调用的方法和方法使用的通信模型类型: 通信类型为 Reque ...

  10. ES6let const

    let const class 类 import 引入模块 export 暴露接口 // var a = 10; // let b = 20; // const c = 30; var和let比 co ...