bzoj4784 [Zjoi2017]仙人掌

40 篇文章 0 订阅
7 篇文章 0 订阅

Description


这里写图片描述
如果一个无自环无重边无向连通图的任意一条边最多属于一个简单环,我们就称之为仙人掌。所谓简单环即不经过
重复的结点的环。
现在九条可怜手上有一张无自环无重边的无向连通图,但是她觉得这张图中的边数太少了,所以她想要在图上连上
一些新的边。同时为了方便的存储这张无向图,图中的边数又不能太多。经过权衡,她想要加边后得到的图为一棵
仙人掌。不难发现合法的加边方案有很多,可怜想要知道总共有多少不同的加边方案。两个加边方案是不同的当且
仅当一个方案中存在一条另一个方案中没有的边。

保证输入的图联通且没有自环与重边
Sigma(n)<=5*10^5,m<=10^6,1<=m<=n*(n-1)/2

Solution


这道题好劲啊,思路只有一点点
首先可以想到一个已有的环是不能再加边的,并且去掉环后不能跨连通块加边,也就是说去掉环边后形成的多棵树互不影响。双连通分量搞一搞然后分别树形dp即可

仙人掌的判定可以用树上差分,即对于一条返祖边就相当于给一段区间加1,如果找到一条边被累加了两次或以上说明不是仙人掌

贴一波题解:

 考虑一棵树,我们设强制每颗子树必然要加一条连到其祖先的边(根节点特判),显然这样的边只能有一条。若本来没有则相当于子树的根节点朝其父亲又连了一条边。那么问题就转化成了有多少种方案使得所有的边都被覆盖一次。
  设f[x]表示以x为根的子树组成了仙人掌且有一条边连向祖先有多少种加边方案。
再设g[x]表示x个点任意匹配的方案数,显然g[x]=g[x-1]+g[x-2]*(x-1)

 那么有两种情况

一种是x的子树内没有边连向祖先,也就是x连了一条边到祖先
那么对f[x]的贡献就是(∏f[son])∗g[size]

,size表示x的儿子数量
简单解释一下,因为x的每颗子树都连了一条边出来,我们可以将两颗子树连出来的边连到一起,也可以将某棵子树的边连到x上,那么就相当于size个节点任意匹配的方案数了。

一种是x的某棵子树内的边连到了其祖先
那么对f[x]的贡献就是(∏f[son])∗g[size1]∗size

因为假设y的子树连了一条到x的祖先的边,那么其贡献就是f[y]∗(∏son!=yf[son])∗g[size1]
就等于(∏f[son])∗g[size1]

,又因为有size种选择,所以就乘上一个size。

那么这道题至此就被解决啦!!! 

Solution


#include <stdio.h>
#include <string.h>
#include <algorithm>
#define rep(i,st,ed) for (int i=st;i<=ed;++i)
#define fill(x) rep(i,0,n) x[i]=0;

typedef long long LL;
const int MOD=998244353;
const int N=500005;
const int E=4000005;

struct edge {int x,y,used,next;} e[E];

int dfn[N],low[N],tot;
int stack[E],top;
int f[N],g[N],c[N];
int ls[N],edCnt;

bool vis[N];

void add_edge(int x,int y) {
    e[++edCnt]=(edge) {x,y,0,ls[x]}; ls[x]=edCnt;
    e[++edCnt]=(edge) {y,x,0,ls[y]}; ls[y]=edCnt;
}

void dfs1(int now) {
    dfn[now]=1;vis[now]=1;
    for (int i=ls[now];i;i=e[i].next) {
        if (!dfn[e[i].y]) dfs1(e[i].y);
        else if (vis[e[i].y]) c[now]++,c[e[i].y]--;
    }
    vis[now]=0;
}

bool dfs2(int now) {
    dfn[now]=1;
    for (int i=ls[now];i;i=e[i].next) {
        if (dfn[e[i].y]) continue;
        if (!dfs2(e[i].y)) return false;
        c[now]+=c[e[i].y];
    }
    return (c[now]<=2);
}

bool check(int n)
{
    fill(dfn); fill(c);
    dfs1(1); fill(dfn);
    return dfs2(1);
}

void dfs(int now,int fa) {
    dfn[now]=low[now]=++tot;
    vis[now]=1;
    for (int i=ls[now];i;i=e[i].next) {
        if (e[i].y==fa) continue;
        if (!dfn[e[i].y]) {
            stack[++top]=i;
            dfs(e[i].y,now);
            low[now]=std:: min(low[now],low[e[i].y]);
            if (low[e[i].y]!=dfn[now]) continue;
            int tmp=0;
            while (tmp!=i) {
                tmp=stack[top--];
                e[tmp].used=e[tmp^1].used=1;
            }
        } else if (vis[e[i].y]) {
            low[now]=std:: min(low[now],dfn[e[i].y]);
            stack[++top]=i;
        }
    }
    vis[now]=0;
    if (dfn[now]==low[now]&&top) top--;
}

void tarjan(int n) {
    fill(vis); fill(dfn); fill(low);
    tot=0; dfs(1,0);
}

void dp(int now,int root) {
    vis[now]=1; f[now]=1;
    int tot=1,cnt=0;
    for (int i=ls[now];i;i=e[i].next) {
        if (vis[e[i].y]||e[i].used) continue;
        dp(e[i].y,now);
        tot=(LL)tot*f[e[i].y]%MOD; cnt++;
    }
    if (now==root) f[now]=(LL)tot*g[cnt]%MOD;
    else f[now]=(LL)tot*(g[cnt]+g[cnt-1]*cnt%MOD)%MOD;
}

void solve(int n) {
    rep(i,0,n) vis[i]=0;
    int ans=1;
    rep(i,1,n) if (!vis[i]) {
        dp(i,i);
        ans=((LL)ans*f[i])%MOD;
    }
    printf("%d\n", ans);
}

void init(int &n,int &m) {
    scanf("%d%d",&n,&m);
    rep(i,0,n) ls[i]=0; edCnt=1;
    rep(i,1,m) {
        int x,y; scanf("%d%d",&x,&y);
        add_edge(x,y);
    }
}

int main(void) {
    freopen("data.in","r",stdin);
    freopen("myp.out","w",stdout);
    g[0]=g[1]=1; g[2]=2; rep(i,3,N-1) g[i]=(g[i-1]+(LL)g[i-2]*(i-1)%MOD)%MOD;
    int T; scanf("%d",&T);
    while (T--) {
        int n,m; init(n,m);
        if (!check(n)) {
            puts("0");
            continue;
        }
        tarjan(n);
        solve(n);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值