题目描述

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

现在九条可怜手上有一张无自环无重边的无向连通图,但是她觉得这张图中的边数太少了,

所以她想要在图上连上一些新的边。同时为了方便的存储这张无向图,图中的边数又不能太多。

经过权衡,她想要加边后得到的图为一棵仙人掌。不难发现合法的加边方案有很多,可怜想要知道总共有多少不同的加边方案。两个加边方案是不同的当且仅当一个方案中存在一条另一个方案中没有的边。

输入格式

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

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

接下来 $m$ 行,每行两个整数 $u, v(1 \le u, v \le n, u \ne v)$ 表示图中的一条边。保证输入的图连通且没有自环与重边。

输出格式

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

限制与约定

测试点编号 $\sum n$ $m$ 其他约定
1 $\le 5$ $\le 10$
2 $\le 2000$ $\le 2 \times 10^5$
3
4 $\le 10^5$ $= n - 1$ 图为一条链
5
6
7
8 $\le 5 \times 10^5$ $\le 10^6$
9
10

对于 100% 的数据,保证 $1 \le m \le \frac{n(n−1)}{2}, \sum m \le 10^6$。

注意 $T$ 可能较大,请注意控制初始化的复杂度。

样例二的四组数据依次满足第 2, 4, 6, 8 个点的条件。

时间限制:$1\texttt{s}$

空间限制:$512\texttt{MB}$


果然我还是too young too simple啊...这么基础的树形dp都撕烤不出来.......


分析1

我们先来考虑树的做法(当然也同时包含了链)

我们用\(f[i]\)表示子树\(i\)的方案数

那么,\(f[i]\)可以由两部分得到,一部分是\(\prod f(y)(y\in Child(x))\),另一部分是在\(Child(x)\)中相互连接得到的

那么我们来考虑一下第二部分答案

对于一个子树\(x\),如果\(x\)不是根节点的话,那么显然他还需要向上拓展,我们可以把他看成\(|Child(x)|+1\)个节点相互连接来做

我们用\(g[i]\)表示\(i\)个孩子互相连接的方案数,\(g[i]=g[i-1]+(i-1)*g[i-2]\)

如果\(i\)这个孩子,不与其他节点连接,那么方案就是\(g[i-1]\),如果与其他点连接,那么有\((i-1)\)中选择方式,而当选择一个点以后,显然有两个点不能再连接,那么方案就是\((i-1)*g[i-2]\),两部分相加即可

那么,根据乘法原理,\(f[i]\)也由两部分相乘得到即可

很巧的是,小数据的10分恰好也是树,那么这样可以得到50分

#include<cstdio>  
#include<iostream>  
#include<algorithm>  
#include<cstdlib>  
#include<cstring>
#include<string>
#include<climits>
#include<vector>
#include<cmath>
#include<map>
#include<set>
#define LL long long
 
using namespace std;
 
inline char nc(){
  static char buf[100000],*p1=buf,*p2=buf;
  if (p1==p2) { p2=(p1=buf)+fread(buf,1,100000,stdin); if (p1==p2) return EOF; }
  return *p1++;
}
 
inline void read(int &x){
  char c=nc();int b=1;
  for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
  for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}
 
inline void read(LL &x){
  char c=nc();LL b=1;
  for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
  for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}

inline int read(char *s)
{
    char c=nc();int len=0;
    for(;!(c>='A' && c<='Z');c=nc()) if (c==EOF) return 0;
    for(;(c>='A' && c<='Z');s[len++]=c,c=nc());
    s[len++]='\0';
    return len;
}

inline void read(char &x){
  for (x=nc();!(x>='A' && x<='Z');x=nc());
}

int wt,ss[19];
inline void print(int x){

    if (x<0) x=-x,putchar('-');
    if (!x) putchar(48); else {
    for (wt=0;x;ss[++wt]=x%10,x/=10);
    for (;wt;putchar(ss[wt]+48),wt--);}
}
inline void print(LL x){
    if (x<0) x=-x,putchar('-');
    if (!x) putchar(48); else {for (wt=0;x;ss[++wt]=x%10,x/=10);for (;wt;putchar(ss[wt]+48),wt--);}
}

int T,n,m,s,flag,v[500010];
vector<int> b[500010];
LL g[500010],f[500010];
const LL mo=998244353;
struct tree
{
    int a,b,dfs,low;
}a[500010];
map<int,bool> c[500010];
void init()
{
    for (int i=1;i<=n;i++)
        b[i].clear(),a[i].dfs=a[i].low=a[i].a=a[i].b=0,c[i].clear();
    g[1]=1LL,g[2]=2LL;
    for (int i=3;i<=n;i++)
        g[i]=(g[i-1]+g[i-2]*(LL)(i-1)%mo)%mo;
}

void dp(int x,int y)
{
    v[x]=1;f[x]=1;
    int sum=0;
    for (int i=0;i<b[x].size();i++)
        if (!v[b[x][i]] && !c[x][b[x][i]])
        {
            dp(b[x][i],y);
            f[x]=f[x]*f[b[x][i]]%mo;
            sum++;
        }
    if (x==y) f[x]=f[x]*g[sum]%mo;
    else f[x]=f[x]*g[sum+1]%mo;
}

int main()
{
    read(T);
    while(T--)
    {
        read(n);read(m);
        int x,y;
        init();
        for (int i=1;i<=m;i++)
            read(x),read(y),b[x].push_back(y),b[y].push_back(x);
        LL res=1;
        for(int i=1;i<=n;i++) v[i]=0;
        for (int i=1;i<=n;i++)
            if (!v[i]) dp(i,i),res=res*f[i]%mo;
        print(res);puts("");
    }
    return 0;
}

分析2

首先我们要判掉一开始就不是仙人掌的情况

可以做一遍dfs,然后计算一下每个节点到根节点的路径数,如果存在一个点\(>2\)的情况,显然不是仙人掌,那么答案一定是\(0\)

接下来,我们发现,环对答案是没有任何的贡献的,那么我们把图中所有的环边去掉,然后对剩下的森林做dp

而对于每一棵独立的树,根据乘法原理,答案就是他们各自方案的乘积

#include<cstdio>  
#include<iostream>  
#include<algorithm>  
#include<cstdlib>  
#include<cstring>
#include<string>
#include<climits>
#include<vector>
#include<cmath>
#include<map>
#include<set>
#define LL long long
 
using namespace std;
 
inline char nc(){
  static char buf[100000],*p1=buf,*p2=buf;
  if (p1==p2) { p2=(p1=buf)+fread(buf,1,100000,stdin); if (p1==p2) return EOF; }
  return *p1++;
}
 
inline void read(int &x){
  char c=nc();int b=1;
  for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
  for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}
 
inline void read(LL &x){
  char c=nc();LL b=1;
  for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
  for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}

inline int read(char *s)
{
    char c=nc();int len=0;
    for(;!(c>='A' && c<='Z');c=nc()) if (c==EOF) return 0;
    for(;(c>='A' && c<='Z');s[len++]=c,c=nc());
    s[len++]='\0';
    return len;
}

inline void read(char &x){
  for (x=nc();!(x>='A' && x<='Z');x=nc());
}

int wt,ss[19];
inline void print(int x){

    if (x<0) x=-x,putchar('-');
    if (!x) putchar(48); else {
    for (wt=0;x;ss[++wt]=x%10,x/=10);
    for (;wt;putchar(ss[wt]+48),wt--);}
}
inline void print(LL x){
    if (x<0) x=-x,putchar('-');
    if (!x) putchar(48); else {for (wt=0;x;ss[++wt]=x%10,x/=10);for (;wt;putchar(ss[wt]+48),wt--);}
}

int T,n,m,s,flag,v[500010];
vector<int> b[500010];
LL g[500010],f[500010];
const LL mo=998244353;
struct tree
{
    int a,b,dfs,low;
}a[500010];
map<int,bool> c[500010];

int dep[500010],dfn[500010],fa[500010],lu[500010],cnt;

void init()
{
    for (int i=1;i<=n;i++)
        b[i].clear(),a[i].dfs=a[i].low=a[i].a=a[i].b=0,c[i].clear(),dfn[i]=0,dep[i]=0,fa[i]=0;
    g[1]=1LL,g[0]=1LL;
    for (int i=2;i<=n;i++)
        g[i]=(g[i-1]+g[i-2]*(LL)(i-1)%mo)%mo;
}

void dfs(int x,int p)
{
    fa[x]=p,dfn[x]=++cnt,dep[x]=dep[p]+1;
    for (int i=0;i<b[x].size();i++)
        if (!dfn[b[x][i]]) dfs(b[x][i],x);
}

bool check()
{
    cnt=0;
    dfs(1,0);
    int u,v;
    memset(lu,0,sizeof(lu));
    for (int i=1;i<=n;i++)
        for (int j=0;j<b[i].size();j++)
        if (i<b[i][j])
        {
            u=i,v=b[i][j];
            if (dfn[u]<dfn[v]) swap(u,v);
            while (u!=v)
            {
                if (lu[u]==2) return false;
                lu[u]++,u=fa[u];
            }
        }
    return true;
}

void dp(int x,int y)
{
    v[x]=1;f[x]=1;
    int sum=0;
    for (int i=0;i<b[x].size();i++)
        if (!v[b[x][i]] && !c[x][b[x][i]])
        {
            dp(b[x][i],y);
            f[x]=f[x]*f[b[x][i]]%mo;
            sum++;
        }
    if (x==y) f[x]=f[x]*g[sum]%mo;
    else f[x]=f[x]*g[sum+1]%mo;
}

void work(int x)
{
    for (int i=0;i<b[x].size();i++)
        if (v[b[x][i]] && b[x][i]!=fa[x])
        {
            int y=b[x][i];
            c[x][y]=1,c[y][x]=1;
            while (x!=y)
            {
                c[x][fa[x]]=1;c[fa[x]][x]=1;
                x=fa[x];
            }
            return ;
        }
        else if (!v[b[x][i]]) 
            v[b[x][i]]=1,fa[b[x][i]]=x,work(b[x][i]),v[b[x][i]]=0;
}

int main()
{
    read(T);
    while(T--)
    {
        read(n);read(m);
        int x,y;
        init();
        for (int i=1;i<=m;i++)
            read(x),read(y),b[x].push_back(y),b[y].push_back(x);
        if (!check()) {print(0),puts("");continue;}
        for(int i=1;i<=n;i++) v[i]=0;
        v[1]=1;work(1);
        LL res=1;
        for(int i=1;i<=n;i++) v[i]=0;
        for (int i=1;i<=n;i++)
            if (!v[i]) dp(i,i),res=res*f[i]%mo;
        print(res);puts("");
    }
    return 0;
}