计数好题

题意:给定简单无向图 \(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. Win10 下Cisco AnyConnect Secure Mobility Client问题(转)

    原文地址:http://blog.sina.com.cn/s/blog_66b9ff210102vup0.html            从WIN8升级到WIN10 ,Cisco AnyConnect ...

  2. redis windows版本安装

    1.redis3.0-win版本微软已停止更新 链接:https://github.com/microsoftarchive/redis 非官方在持续更新 链接:https://github.com/ ...

  3. typescript 的动态引入组件

    环境: Arco Pro + Vue3 vite自身对动态字符串形式的组件引入是有限制的, 以下写法会报错 官方文档中也对此有做说明, 只能通过固定形式去引用 以下形式不会报错, 但这种固定格式的局限 ...

  4. redis的数据操作和python操作redis+关系非关系数据库差异

    关系型数据库(RMDBS) 数据库中表与表的数据之间存在某种关联的内在关系,因为这种关系,所以我们称这种数据库为关系型数据库. 典型:Mysql/MariaDB.postgreSQL.Oracle.S ...

  5. 入门IDEA

    Hello world psvm sout public class HelloWord { public static void main(String[] args) { System.out.p ...

  6. Python笔记--练习题(都来瞧一瞧,看一看嘞)

    利用Python对文件进行操作 重新写入的文件如下图所示: 统计学生成绩文件的最高分最低分和平均分 Python如何统计英文文章出现最多的单词 Python统计目录下的文件大小 Python按照文件后 ...

  7. ISCC 2022 RE

    ISCC 2022 RE 练武题 Amy's Code v9=[0]*20 v9[0] = 149 v9[1] = 169 v9[2] = 137 v9[3] = 134 v9[4] = 212 v9 ...

  8. Linux & 标准C语言学习 <DAY1>

    Linux系统简单介绍:     BCPL->New B->C->UNIX->Minix->Linux->gcc     美国贝尔实验室 1968     Linu ...

  9. Windows7蓝牙音响连接成功,但是无法播放音乐,没有声音

    本人使用的蓝牙是博通94360CD的无线网卡集成的,在Windows7系统,成功安装蓝牙驱动,但是无论连接什么蓝牙音响设备,都可以连接成功,但是在音频管理却没有蓝牙音响的设备,自然就没有声音.后来找到 ...

  10. JS逆向实战12——某店 captchaToken 参数 加密

    今天爬取的是网站需要模拟登陆 目标网站 aHR0cHM6Ly9wYXNzcG9ydC55aGQuY29tL3Bhc3Nwb3J0L2xvZ2luX2lucHV0LmRv 浏览器抓包分析 随便输入一堆假 ...