[JZOJ4949]仙人球

题目大意

定义仙人球为一个无自环重边的、每个点最多属于一个简单环的无向连通图
现在给定一个 n 个点m条边的仙人球,你要从其中选出至多 K 个,使得这些点满足能在不经过其它没被选择的点的情况下互相连通。不能不选点。
求出方案数模109+7

1n5×103,1Kmin(n,100)


题目分析

可以发现仙人球就是一堆环形成一个树形结构(孤立的单点也可以看做环)。
那么我们考虑使用动态规划。
fi,j 表示做到第 i 个环,以这个环为根的子仙人球总共选择了j个节点,并且当前环的深度最小的点一定选择了的方案数。
考虑将环上每个点为根的子仙人球的信息放到点上,令 hi,j 表示当前环第 i 个点为根的子仙人球(如下图颜色圈中的各个块)选择了j个节点的方案数。
cactus1
hi 可以很方便地由第 i 个点的所有儿子的f推出。
那么现在考虑知道了 hi 怎么求出 f
现在我们相当于在一个环上有若干个关键点,第i个关键点选择 j 个点有hi,j种方案。求整个环从 1 号点(环深度最小的点即顶点)开始向两边若干连续关键点选出点的方案。
我们令F0,i,j表示从点 1 向后扩展到点i,每个关键点都至少选出一个点,总共选出 j 个点的方案数。F1,i,j则是从最后一个点向前扩展到点 i ,其它类似。
cactus2
F0/1,i,j通过 hi,j 易得。
tot 为环上关键点个数。我们得出这个以后,就可以通过

fx,j=F0,tot,j+i=1totF0,i,kl=i+2totF1,l,jk

来计算 f 的值,后面那个sigma显然可以使用后缀和维护,而为什么是从i+2开始枚举呢?因为如果我们算上 i+1 ,那就会把整个环的关键点都有点选的情况计算上 tot 次。而式子前面那个 F0,tot,j 就是把这种情况一次性算了。
那么知道了 f ,我们怎么统计答案呢?
gi,j表示做到第 i 个环,以这个环为根的子仙人球总共选择了j个节点,并且当前环至少选了一个点的方案数。我们求答案时把所有 gi,j 加起来就行了。
怎么计算 g 呢?g相当于在环上的连续一段关键点中选点。我们分两种情况讨论:一种是这连续的一段没有跨过第一个点和最后一个点的连边,另一种是这连续的一段跨过了第一个点和最后一个点的连边。
我们令 Gi,j 表示环上第 i 个关键点向前连续选点,选了j个的方案数,方程和 F 是有点类似的。那么所有的Gi,j求和就是第一种情况。
至于第二种情况呢?可以发现它其实就是从第一个点和最后一个点分别向两边延伸,和 f 的计算过程是差不多的,唯一的不同就是从最后一个点延伸那边不能一个点都不选,不然就会算重。
计算过程中不需要显式地弄g出来,我只是为了统计方便弄了这个辅助变量。
所有过程都是 O(nK2) 的。


代码实现

#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cctype>
#include <stack>

using namespace std;

int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
    while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    return x*f;
}

const int P=1000000007;
const int N=5050;
const int M=N<<1;
const int E=M<<1;
const int MAXK=105;

int f[N][MAXK],g[N][MAXK],h[N][MAXK],F[2][N][MAXK],G[N][MAXK],sum[MAXK];
int last[N],bel[N],DFN[N],low[N];
int tov[E],rev[E],nxt[E];
stack<int> st;
int cir[N][N];
int idx,cnt,n,m,tot,K,ans,root;

void insert(int x,int y,int r){tov[++tot]=y,rev[tot]=tot+r,nxt[tot]=last[x],last[x]=tot;}

void tarjan(int x,int fe)
{
    DFN[x]=low[x]=++idx,st.push(x);
    for (int i=last[x],y;i;i=nxt[i])
        if (i!=fe)
            if (!DFN[y=tov[i]]) tarjan(y,rev[i]),low[x]=min(low[x],low[y]);
            else low[x]=min(low[x],DFN[y]);
    if (DFN[x]==low[x])
    {
        ++cnt;
        int y;
        do{y=st.top(),cir[bel[y]=cnt][++cir[cnt][0]]=y,st.pop();}while (x!=y);
        for (int i=1;i*2<=cir[cnt][0];i++) swap(cir[cnt][i],cir[cnt][cir[cnt][0]+1-i]);
        if (x==1) root=cnt;
    }
}

void dp(int cid,int fa)
{
    int son=cir[cid][0];
    for (int i,x,y,j=1;j<=son;j++)
        for (i=last[x=cir[cid][j]];i;i=nxt[i])
            if ((y=tov[i])!=fa&&bel[y]!=cid) dp(bel[y],x);
    for (int i,x,y,u,j=1;j<=son;j++)
    {
        for (i=0;i<=K;i++) h[j][i]=0;
        h[j][1]=1;
        for (i=last[x=cir[cid][j]];i;i=nxt[i])
            if ((y=tov[i])!=fa&&(u=bel[y])!=cid)
                for (int k=K;k>=1;k--)
                    for (int l=1;l<=K-k;l++)
                        (h[j][k+l]+=1ll*h[j][k]*f[u][l]%P)%=P;
    }
    /*calculate G*/
    for (int i=1;i<=K;i++) G[1][i]=h[1][i];
    for (int i=2;i<=son;i++)
        for (int j=K;j>=1;j--)
        {
            G[i][j]=h[i][j];
            for (int k=1;k<=K-j;k++) (G[i][j+k]+=1ll*G[i-1][j]*h[i][k]%P)%=P;
        }
    for (int i=1;i<=son;i++)
        for (int j=1;j<=K;j++)
            (g[cid][j]+=G[i][j])%=P;
    /*calculate F*/
    for (int i=1;i<=K;i++) F[0][1][i]=h[1][i];
    for (int i=2;i<=son;i++)
        for (int j=K;j>=1;j--)
        {
            F[0][i][j]=0;
            for (int k=1;k<=K-j;k++) (F[0][i][j+k]+=1ll*F[0][i-1][j]*h[i][k]%P)%=P;
        }
    for (int i=1;i<=K;i++) F[1][son][i]=h[son][i];
    for (int i=son-1;i>=1;i--)
        for (int j=K;j>=1;j--)
        {
            F[1][i][j]=0;
            for (int k=1;k<=K-j;k++) (F[1][i][j+k]+=1ll*F[1][i+1][j]*h[i][k]%P)%=P;
        }
    for (int i=0;i<=K;i++) sum[i]=0;
    for (int i=1;i<=K;i++) f[cid][i]=F[1][1][i];
    sum[0]=1;
    for (int i=son-1;i>=1;i--)
    {
        for (int j=1;j<=K;j++)
            for (int k=0;k<=K-j;k++)
            {
                (f[cid][j+k]+=1ll*F[0][i][j]*sum[k]%P)%=P;
                if (k) (g[cid][j+k]+=1ll*F[0][i][j]*sum[k]%P)%=P;
            }
        for (int j=1;j<=K;j++) (sum[j]+=F[1][i+1][j])%=P;
    }
}

void calc()
{
    ans=0;
    for (int i=1;i<=cnt;i++)
        for (int j=1;j<=K;j++)
            (ans+=g[i][j])%=P;
}

int main()
{
    freopen("cactus.in","r",stdin),freopen("cactus.out","w",stdout);
    n=read(),m=read(),K=read();
    for (int i=1,x,y;i<=m;i++) x=read(),y=read(),insert(x,y,1),insert(y,x,-1);
    tarjan(1,0),dp(root,0),calc();
    printf("%d\n",ans);
    fclose(stdin),fclose(stdout);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值