原文链接 https://www.cnblogs.com/cly-none/p/ZJOI2017cactus.html

给出一个\(n\)个点\(m\)条边的无向连通图,求有多少种加边方案,使得加完后得到一个仙人掌。

\(n \leq 5 \times 10^5, \ m \leq 10^6\)

首先,判定无解后,我们可以把每个环删掉,那么答案就是剩下的若干树的加边方案的乘积。

于是就考虑一棵树怎么做。

sol1

令\(dp_i\)表示在结点\(i\)的子树中的答案。考虑如何转移。

注意到,假如我们把\(i\)和它子树中的某个点\(v\)连一条边,那么这样得到的方案数就与\(i\)到\(v\)的链有关。对于链上最后一个点即\(v\),它能产生\(dp_v\)的贡献;否则,对于链上的点\(a\),若它在链上的下一个(深度较大的)点是\(b\),那么\(a\)能产生的贡献就是\(a\)删掉\(b\)这个子树后的dp值。整条链能产生的方案数就是所有贡献的乘积。

于是我们令\(sdp_i\)表示在\(i\)的子树中,以\(i\)为一段的所有链的方案数之和。这记录的是这个子树的所有向\(i\)的父亲连边的方案数。(不连边其实就是\(i\)向外连)

再考虑一棵子树向外连边有两种情况,一是直接连到当前的根上,二是和另一颗子树配对。

所以我们令\(g_i\)表示\(i\)个元素,每个元素都能任意配对也能不配对(即连到根上)的方案数。那么就有\(dp_i = \prod_{v \in child_i} sdp_v g_{|child_i|}\)。其中\(child_i\)表示结点\(i\)的所有孩子构成的集合。

考虑如何计算\(g_n\)。我们可以枚举元素\(n\)的状态。有两种可能:

  • 不匹配。即\(g_{n-1}\)。
  • 匹配。那么枚举它和哪个元素匹配。即\((n-1)g_{n-2}\)。

于是就能计算出所有\(g_n\)了。

用类似的方法也能算出\(sdp_i\)。可以用前缀积和后缀积来避免计算逆元,做到\(O(n)\)复杂度。

下面代码是\(O(n \log n)\)的,\(\log n\)在计算逆元上。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
typedef pair<int,int> pii;
#define fir first
#define sec second
#define rep(i,a,b) for (int i = (a) ; i <= (b) ; ++ i)
#define rrp(i,a,b) for (int i = (a) ; i >= (b) ; -- i)
#define gc() getchar()
template <typename tp>
inline void read(tp& x) {
x = 0; char tmp; bool key = 0;
for (tmp = gc() ; !isdigit(tmp) ; tmp = gc())
key = (tmp == '-');
for ( ; isdigit(tmp) ; tmp = gc())
x = (x << 3) + (x << 1) + (tmp ^ '0');
if (key) x = -x;
} const int N = 500010, M = 1000010, MOD = 998244353;
int power(int a,int b) {
int ret = 1;
while (b) {
if (b & 1) ret = 1ll * ret * a % MOD;
a = 1ll * a * a % MOD;
b >>= 1;
}
return ret;
}
inline void Add(int& x,int y) {
x = x + y >= MOD ? x + y - MOD : x + y;
}
struct edge {
int la,b;
} con[M << 1];
int tot,fir[N],n,m,ans;
void add(int from,int to) {
con[++tot] = (edge) {fir[from], to};
fir[from] = tot;
}
int dfn[N], low[N], col[N], sta[N], top, ecnt, ccnt;
pii edg[M];
void dfs(int pos,int fa) {
sta[low[pos] = dfn[pos] = ++ top] = pos;
for (int i = fir[pos] ; i ; i = con[i].la) {
if (con[i].b == fa) continue;
if (dfn[con[i].b]) low[pos] = min(low[pos], dfn[con[i].b]);
else {
dfs(con[i].b, pos);
low[pos] = min(low[pos], low[con[i].b]);
if (low[con[i].b] >= dfn[pos]) {
if (low[con[i].b] > dfn[pos])
edg[++ecnt] = pii(pos, con[i].b);
else ++ ccnt;
top = dfn[pos];
}
}
}
}
int dp[N], sdp[N], sz[N], jc[N], inv[N], vis[N];
void fsd(int pos,int fa) {
vis[pos] = 1;
dp[pos] = 1;
sz[pos] = 1;
int num = 0;
for (int i = fir[pos] ; i ; i = con[i].la) {
if (con[i].b == fa) continue;
fsd(con[i].b, pos);
sz[pos] += sz[con[i].b];
dp[pos] = 1ll * sdp[con[i].b] * dp[pos] % MOD;
++ num;
}
int tmp = 0, tmp1 = 0;
for (int k = 0, ipw2 = 1 ; k * 2 <= num ; ++ k) {
Add(tmp,1ll * jc[num] * inv[k] % MOD * inv[num - 2 * k] % MOD * ipw2 % MOD);
ipw2 = 1ll * ipw2 * (MOD + 1) / 2 % MOD;
}
-- num;
for (int k = 0, ipw2 = 1 ; k * 2 <= num ; ++ k) {
Add(tmp1,1ll * jc[num] * inv[k] % MOD * inv[num - 2 * k] % MOD * ipw2 % MOD);
ipw2 = 1ll * ipw2 * (MOD + 1) / 2 % MOD;
}
for (int i = fir[pos] ; i ; i = con[i].la) {
if (con[i].b == fa) continue;
Add(sdp[pos], 1ll * dp[pos] * power(sdp[con[i].b], MOD-2) % MOD * tmp1 % MOD * sdp[con[i].b] % MOD);
}
dp[pos] = 1ll * dp[pos] * tmp % MOD;
Add(sdp[pos], dp[pos]);
}
void init() {
memset(fir,0,sizeof(int) * (n + 5));
memset(dfn,0,sizeof(int) * (n + 5));
tot = ecnt = ccnt = top = 0;
ans = 1;
memset(dp,0,sizeof(int) * (n + 5));
memset(sdp,0,sizeof(int) * (n + 5));
memset(vis,0,sizeof(int) * (n + 5));
}
void solve() {
read(n), read(m);
init();
jc[0] = 1;
rep (i, 1, n) jc[i] = 1ll * jc[i-1] * i % MOD;
inv[n] = power(jc[n], MOD - 2);
rrp (i, n-1, 0) inv[i] = 1ll * inv[i+1] * (i+1) % MOD;
for (int i = 1, x, y ; i <= m ; ++ i) {
read(x), read(y);
add(x,y);
add(y,x);
}
rep (i, 1, n) if (!dfn[i]) dfs(i, 0);
if (n - 1 + ccnt != m) {
puts("0");
return;
}
tot = 0;
memset(fir,0,sizeof(int) * (n + 5));
rep (i, 1, ecnt) {
add(edg[i].fir, edg[i].sec);
add(edg[i].sec, edg[i].fir);
}
rep (i, 1, n) {
if (vis[i]) continue;
fsd(i, 0);
ans = 1ll * dp[i] * ans % MOD;
}
ans = (ans % MOD + MOD) % MOD;
printf("%d\n", ans);
}
int main() {
int T;
read(T);
while (T --)
solve();
return 0;
}

sol2

上面做法对链的分析太复杂了,于是我们考虑直接用组合意义计算\(sdp_i\)。

分两种情况:

  • 让结点\(i\)负责向上连边。那么答案就是\(\prod_{v \in child_i} sdp_v g_{|child_i|}\)。
  • 让\(i\)的某个孩子内的结点向上连边。我们就枚举这是哪个孩子内的结点。即\(|child_i| \times \prod_{v \in child_i} sdp_v g_{|child_i|-1}\)。

这样就简单多了。

小结:这道题的巧妙之处在于对一些组合意义的运用,比较常规,且直接大力分析也能解决。

【做题】ZJOI2017仙人掌——组合计数的更多相关文章

  1. FJOI2020 的两道组合计数题

    最近细品了 FJOI2020 的两道计数题,感觉抛开数据范围不清还卡常不谈里面的组合计数技巧还是挺不错的.由于这两道题都基于卡特兰数的拓展,所以我们把它们一并研究掉. 首先是 D1T3 ,先给出简要题 ...

  2. [总结]数论和组合计数类数学相关(定理&证明&板子)

    0 写在前面 0.0 前言 由于我太菜了,导致一些东西一学就忘,特开此文来记录下最让我头痛的数学相关问题. 一些引用的文字都注释了原文链接,若侵犯了您的权益,敬请告知:若文章中出现错误,也烦请告知. ...

  3. 【BZOJ5323】[JXOI2018]游戏(组合计数,线性筛)

    [BZOJ5323][JXOI2018]游戏(组合计数,线性筛) 题面 BZOJ 洛谷 题解 显然要考虑的位置只有那些在\([l,r]\)中不存在任意一个约数的数. 假设这样的数有\(x\)个,那么剩 ...

  4. 【BZOJ3142】[HNOI2013]数列(组合计数)

    [BZOJ3142][HNOI2013]数列(组合计数) 题面 BZOJ 洛谷 题解 唯一考虑的就是把一段值给分配给\(k-1\)天,假设这\(k-1\)天分配好了,第\(i\)天是\(a_i\),假 ...

  5. 【BZOJ4830】[HNOI2017]抛硬币(组合计数,拓展卢卡斯定理)

    [BZOJ4830][HNOI2017]抛硬币(组合计数,拓展卢卡斯定理) 题面 BZOJ 洛谷 题解 暴力是啥? 枚举\(A\)的次数和\(B\)的次数,然后直接组合数算就好了:\(\display ...

  6. 【Luogu4931】情侣?给我烧了! 加强版(组合计数)

    [Luogu4931]情侣?给我烧了! 加强版(组合计数) 题面 洛谷 题解 戳这里 忽然发现我自己推的方法是做这题的,也许后面写的那个才是做原题的QwQ. #include<iostream& ...

  7. 【Luogu4921】情侣?给我烧了!(组合计数)

    [Luogu4921]情侣?给我烧了!(组合计数) 题面 洛谷 题解 很有意思的一道题目. 直接容斥?怎么样都要一个平方复杂度了. 既然是恰好\(k\)对,那么我们直接来做: 首先枚举\(k\)对人出 ...

  8. [BZOJ4784][ZJOI2017]仙人掌(树形DP)

    4784: [Zjoi2017]仙人掌 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 312  Solved: 181[Submit][Status] ...

  9. 一道组合数问题--出自 曹钦翔_wc2012组合计数与动态规划

    一道组合数问题--出自 曹钦翔_wc2012组合计数与动态规划 [问题描述] 众所周知,xyc 是一个宇宙大犇,他最近在给他的学弟学妹们出模拟赛. 由于 xyc 实在是太巨了,他出了一套自认为很水的毒 ...

随机推荐

  1. react将字符串转义成html语句

    在使用reactjs库的时候,会遇到将一段html的字符串,然后要将它插入页面中以html的形式展现,然而直接插入的话页面显示的就是这段字符串,而不会进行转义,可以用以下方法插入,便可以html的形式 ...

  2. static与volatile的用法

      static 1.概述 static 声明的变量在C语言中有两方面的特征: 1).变量会被放在程序的全局存储区中,这样可以在下一次调用的时候还可以保持原来的赋值.这一点是它与堆栈变量和堆变量的区别 ...

  3. VUE-005-axios常用请求参数设置方法

    在前后端分离的开发过程中,经常使用 axios 进行后端接口的访问. 个人习惯常用的请求参数设置方法如下所示: // POST方法:data在请求体中 addRow(data) { return th ...

  4. Github上36893颗星!这个被称为下一代企业级应用首选技术你学了么?

    ​ 用一句话概括:这个技术,是JAVA后端框架的龙头老大,执牛耳者.这个技术就是: Spring Boot春靴. Spring Boot到底凭什么成为Java社区最具影响力的项目?说直白点,他爹Spr ...

  5. Azure架构(一):云计算基础

    云计算的定义 云计算(英语:cloud computing),是一种基于互联网的计算方式,通过这种方式,共享的软硬件资源和信息可以按需求提供给使用各种计算终端(桌面电脑.笔记本电脑.平板电脑.手机等) ...

  6. AIX 7.1 RAC 11.2.0.4.0升级至11.2.0.4.6(一个patch跑了3个小时)

    1.环境 DB:两节点RAC 11.2.0.4.0升级至11.2.0.4.6 OS:AIX 7.1(205G内存 16C) 2.节点1.节点2(未建库) 2.1.patch 20420937居然用了3 ...

  7. python-爬虫(3)---lxml匹配css

    百度首页  部分代码 <div class="s_tab_inner"> <b>网页</b> <a href="//www.ba ...

  8. windows----------telnet不是内部命令问题

    1.

  9. CentOS 7 部署GitLab

    GitLab概述 : 是一个利用 Ruby on Rails 开发的开源应用程序,实现一个自托管的Git项目仓库,可通过 Web 界面进行访问公开的戒者私人项目.Ruby on Rails 是一个可以 ...

  10. 【Linux】Jenkins安装(二)

    Jenkins介绍 Jenkins是基于Java开发的一种持续集成工具,用于监控持续重复的工作,功能包括: 1.持续的软件版本发布/测试项目. 2.监控外部调用执行的工作. 安装环境 操作系统:lin ...