@题目描述@

如果一个无自环无重边无向连通图的任意一条边最多属于一个简单环,我们就称之为仙人掌。所谓简单环即不经过重复的结点的环。

现在九条可怜手上有一张无自环无重边的无向连通图,但是她觉得这张图中的边数太少了, 所以她想要在图上连上一些新的边。同时为了方便的存储这张无向图,图中的边数又不能太多。 经过权衡,她想要加边后得到的图为一棵仙人掌。

不难发现合法的加边方案有很多,可怜想要知道总共有多少不同的加边方案。

两个加边方案是不同的当且仅当一个方案中存在一条另一个方案中没有的边。

输入格式

多组数据,第一行输入一个整数 T 表示数据组数。

每组数据第一行输入两个整数 n, m,表示图中的点数与边数。

接下来 m 行,每行两个整数 u, v(1 <= u, v <= n, u ≠ v) 表示图中的一条边。

保证输入的图联通且没有自环与重边。

输出格式

对于每组数据,输出一个整数表示方案数,当然方案数可能很大,请对 998244353 取模后输出。

样例

样例输入

2

3 2

1 2

1 3

5 4

1 2

2 3

2 4

1 5

样例输出

2

8

数据范围与提示

1 <= ∑n <= 5*10^5,1 <= ∑m <= 10^6。

@solution@

如果所有简单环都没有公共边,则一个图是仙人掌。

我们跑一遍 tarjan,将所有返祖边对应的树链标记加一。最后如果有一条边标记 > 1 则不为仙人掌。

然后初始图肯定得是一棵仙人掌。显然我们不可能跨越环连边,于是可以把所有环上的边去掉。

剩下的图变成了一棵不相交的森林。森林之间不能连边,我们只能在树上连求方案数,再把所有树的方案数相乘。

怎么求一棵树连成仙人掌的方案数?一样要求所有简单环都没有公共边,则我们相当于选出若干条没有公共边的链,求方案数。

接下来?树形 dp?貌似没有什么高效的 dp 方法。

因为没有重边,所以我们选出来的链长度 > 1。我们再把问题进一步转化:将没有被选在链中去的那些边单独作一条链,则用边不相交的链覆盖树上所有边的方案唯一对应选出若干条没有公共边的链的方案。

接着考虑每个点的贡献,设点 i 的度数为 d[i]。则我们可以将 i 连出去的边两两配对(也可以不配对),表示 i 所相邻的配对的那些边在同一条链里。

记 f[i] 表示 i 条边互相匹配的方案数,则最终答案为 f[d[i]] 之积。

f[i] 很好求,有递推式 f[i] = f[i-1] + (i-1)*f[i-2]。前者表示不匹配,后者表示选一个匹配。

@accepted code@

#include<cstdio>
#define rep(G, x) for(Graph::edge *p = G.adj[x];p;p = p->nxt)
const int MAXN = 500000;
const int MAXM = 1000000;
const int MOD = 998244353;
struct Graph{
struct edge{
int to; edge *nxt;
}edges[2*MAXM + 5], *adj[MAXN + 5], *ecnt;
void clear(int n) {
ecnt = &edges[0];
for(int i=1;i<=n;i++)
adj[i] = NULL;
}
void addedge(int u, int v) {
edge *p = (++ecnt);
p->to = v, p->nxt = adj[u], adj[u] = p;
p = (++ecnt);
p->to = u, p->nxt = adj[v], adj[v] = p;
}
}G1, G2;
int f[MAXN + 5];
void init() {
f[0] = f[1] = 1;
for(int i=2;i<=MAXN;i++)
f[i] = (f[i - 1] + 1LL * (i - 1) * f[i - 2] % MOD) % MOD;
}
bool flag;
int d[MAXN + 5], s[MAXN + 5];
int dfn[MAXN + 5], dcnt;
void clear(int n) {
G1.clear(n), G2.clear(n);
for(int i=1;i<=n;i++)
d[i] = s[i] = dfn[i] = 0;
dcnt = 0, flag = true;
}
void tarjan(int x, int fa) {
dfn[x] = (++dcnt);
rep(G1, x) {
if( p->to == fa ) continue;
if( dfn[p->to] ) {
if( dfn[p->to] < dfn[x] )
s[x]++, s[p->to]--;
}
else tarjan(p->to, x), G2.addedge(x, p->to);
}
}
int dfs(int x, int fa) {
int ret = s[x];
rep(G2, x) {
if( p->to == fa ) continue;
int del = dfs(p->to, x);
ret += del;
if( del == 0 )
d[x]++, d[p->to]++;
else if( del >= 2 )
flag = false;
}
return ret;
}
void solve() {
int n, m; scanf("%d%d", &n, &m), clear(n);
for(int i=1;i<=m;i++) {
int u, v; scanf("%d%d", &u, &v);
G1.addedge(u, v);
}
tarjan(1, 0), dfs(1, 0);
if( flag ) {
int ans = 1;
for(int i=1;i<=n;i++)
ans = 1LL * ans * f[d[i]] % MOD;
printf("%d\n", ans);
}
else puts("0");
}
int main() {
init(); int T; scanf("%d", &T);
while( T-- ) solve();
}

@details@

坚定了我对于 ZJOI 的题都是不可做的题的决心。

@loj - 2250@ 「ZJOI2017」仙人掌的更多相关文章

  1. 「ZJOI2017」仙人掌

    「ZJOI2017」仙人掌 题目大意: 给定一张无向联通图,求有多少种本质不同的不加重边的加边方案使得新图是个仙人掌. 解题思路: 如果原来的图不是仙人掌,那么答案就是 \(0\) ,否则求出这个仙人 ...

  2. Loj #2570. 「ZJOI2017」线段树

    Loj #2570. 「ZJOI2017」线段树 题目描述 线段树是九条可怜很喜欢的一个数据结构,它拥有着简单的结构.优秀的复杂度与强大的功能,因此可怜曾经花了很长时间研究线段树的一些性质. 最近可怜 ...

  3. Loj #2192. 「SHOI2014」概率充电器

    Loj #2192. 「SHOI2014」概率充电器 题目描述 著名的电子产品品牌 SHOI 刚刚发布了引领世界潮流的下一代电子产品--概率充电器: 「采用全新纳米级加工技术,实现元件与导线能否通电完 ...

  4. Loj #3096. 「SNOI2019」数论

    Loj #3096. 「SNOI2019」数论 题目描述 给出正整数 \(P, Q, T\),大小为 \(n\) 的整数集 \(A\) 和大小为 \(m\) 的整数集 \(B\),请你求出: \[ \ ...

  5. Loj #3093. 「BJOI2019」光线

    Loj #3093. 「BJOI2019」光线 题目描述 当一束光打到一层玻璃上时,有一定比例的光会穿过这层玻璃,一定比例的光会被反射回去,剩下的光被玻璃吸收. 设对于任意 \(x\),有 \(x\t ...

  6. Loj #3089. 「BJOI2019」奥术神杖

    Loj #3089. 「BJOI2019」奥术神杖 题目描述 Bezorath 大陆抵抗地灾军团入侵的战争进入了僵持的阶段,世世代代生活在 Bezorath 这片大陆的精灵们开始寻找远古时代诸神遗留的 ...

  7. Loj #2542. 「PKUWC2018」随机游走

    Loj #2542. 「PKUWC2018」随机游走 题目描述 给定一棵 \(n\) 个结点的树,你从点 \(x\) 出发,每次等概率随机选择一条与所在点相邻的边走过去. 有 \(Q\) 次询问,每次 ...

  8. Loj #3059. 「HNOI2019」序列

    Loj #3059. 「HNOI2019」序列 给定一个长度为 \(n\) 的序列 \(A_1, \ldots , A_n\),以及 \(m\) 个操作,每个操作将一个 \(A_i\) 修改为 \(k ...

  9. Loj #3056. 「HNOI2019」多边形

    Loj #3056. 「HNOI2019」多边形 小 R 与小 W 在玩游戏. 他们有一个边数为 \(n\) 的凸多边形,其顶点沿逆时针方向标号依次为 \(1,2,3, \ldots , n\).最开 ...

随机推荐

  1. 移动端适配(rem单位定义方法)

    注:移动端必须写: <meta name="viewport" content="width=device-width, user-scalable=no, ini ...

  2. Intent 传递Map数据

    android开发默认情况下,通过Bundle bundle=new Bundle();传递值是不能直接传递map对象的,解决办法: 第一步:封装自己的map,实现序列化即可 /** *序列化map供 ...

  3. Oracle锁表查杀会话进程

    一.逐条--锁表 (1)查表名 和 sessionidselect b.owner,b.object_name,a.session_id,a.locked_mode from v$locked_obj ...

  4. tcpdump的表达式介绍

    表达式是一个正则表达式,tcpdump利用它作为过滤报文的条件,如果一个报文满足表 达式的条件,则这个报文将会被捕获.如果没有给出任何条件,则网络上所有的信息包 将会被截获. 在表达式中一般如下几种类 ...

  5. JS---案例:拖曳对话框

    案例:拖曳对话框 ps: 实际没有要拖曳登录框的需求,只是演示拖曳的这个效果 1. 获取超链接,注册点击事件,显示登陆框和遮挡层 2. 获取关闭,注册点击事件,隐藏登陆框和遮挡层 3. 按下鼠标,移动 ...

  6. 产生冠军 HDU - 2094 (拓扑排序)

    分析: 当有且只有一个节点入度为0时,该节点即为冠军,否则不能产生冠军.所以以下代码中只要入度大于0的无论是几都将其设置为1. #include <stdio.h> #include &l ...

  7. HTML5小知识汇总

    1.关于<!DOCTYPE HTML> H5只需要<!DOCTYPE HTML>这样简单的声明,不用之前一长串代码,因为H5不是基于SGML,所以不需要对DTD引用,但是需要D ...

  8. 调用Lua脚本print(xxx)报attempt to call a nil value (global 'print')错误

    在自己程序里调用Lua脚本print(xxx) 报出attempt to call a nil value (global 'print')错误 解决方法: luaopen_base(L); 或者 l ...

  9. phpcms 按价格、按销量、按时间等排序实现思路

    大体思路是在链接中加入指定排序的参数,例如我们使用get中的order作为排序参数: order=views 人气:order=sells 效率:order=pirce 按价格: 那么这三个排序按钮的 ...

  10. Elasticsearch 启动需要密码?

    vagrant@homestead:~$ systemctl disable elasticsearch.service Synchronizing state of elasticsearch.se ...