【Codeforces 512D】Fox And Travelling

拓扑排序,树形 DP。

题目大意:

给定一张 $n$ 个点 $m$ 条边的无向图。现在要遍历一些。一个点能被遍历,当且仅当与它相连的点中,至多一个点未被遍历。

现在对于所有 $k\in[1,n]$,求遍历 $k$ 个点,有多少不同的方案。

两个方案不同,当且仅当两个方案遍历的点存在不同,或遍历的顺序不同。

题解:

在环上的点显然是不可能遍历到的。被两个环夹在中间的点也不可能遍历到。我们可以用拓扑排序找到能遍历到的点和不能遍历到的点。而能遍历到的点,形成若干棵树。

考虑两种情况:

  1. 树上存在一个结点,和不能被遍历到的点相连。
  2. 树上不存在一个结点,和不能遍历到的点相连。

对于情况 $1$,这个结点只能是最后被遍历到的结点,我们令这个结点为根进行 dp。

对于情况 $2$,所有结点都可能是最后遍历到的,我们对所有点,都将其当根进行一次 dp。

具体地,令 $f[i][j]$ 表示以 $i$ 为根的子树中,遍历 $j$ 个点的方案数。

对于情况 $1$,这整棵树的 dp 值就是根的 dp 值。

对于情况 $2$,这整棵树的 dp 值是所有结点为根的 dp 值之和。但是对于任意一种选了 $j$ ($j\lt n$)个点的方案,在没选的 $n-j$ 个点作为根的时候都算了一遍,因此还要除以 $n-j$。

然后把每棵树的 dp 值再合并即可。合并的转移和上面一样。

由于是树上背包问题,所以时间复杂度可以优化至 $O(n^3)$。

Code:

#include<cstdio>
#include<vector>
#include<cstring>
#include<queue>
using namespace std;
typedef long long LL;
const int md=1e9+9,N=105;
int fac[N],iv[N],iiv[N],n,m,deg[N],root[N],sz[N],size[N];
bool inc[N],flag[N];
int s[N],dp[N][N],g[N][N];
vector<int>G[N];
inline int C(int n,int m){return(LL)fac[n]*iv[m]%md*iv[n-m]%md;}
inline void upd(int&a){a+=a>>31&md;}
inline void merge(int*a,int*b,int*c,int lim){
    static int A[N];
    for(int i=0;i<=lim;++i){
        A[i]=0;
        for(int j=0;j<=i;++j)
        A[i]=(A[i]+(LL)b[j]*c[i-j]%md*C(i,j))%md;
    }
    for(int i=0;i<=lim;++i)a[i]=A[i];
    for(int i=lim+1;i<=n;++i)a[i]=0;
}
void topo(){
    queue<int>q;
    for(int i=1;i<=n;++i)if(deg[i]<=1)q.push(i);
    while(!q.empty()){
        int u=q.front();
        q.pop();
        inc[u]=0;
        for(int to:G[u])
        if(--deg[to]<2&&inc[to])q.push(to);
    }
}
inline bool getflag(int u){
    bool&fg=flag[u];
    for(int to:G[u])
    if(inc[to])return fg=1;
    return 0;
}
void DfS(int now,int rt){
    root[now]=rt,sz[now]=1,flag[now]=flag[rt];
    for(int to:G[now])
    if(!inc[to]&&!root[to]){
        DfS(to,rt);
        sz[now]+=sz[to];
    }
}
void dFs(int now,int rt,int pre){
    sz[now]=1;
    for(int to:G[now])
    if(!inc[to]&&root[to]==rt&&to!=pre){
        dFs(to,rt,now);
        sz[now]+=sz[to];
    }
}
void dfs(int now,int pre){
    memset(dp[now],0,sizeof*dp);
    dp[now][0]=1;
    int lim=0;
    for(int to:G[now])if(to!=pre&&root[to]==root[now]){
        dfs(to,now);
        lim+=sz[to];
        merge(dp[now],dp[now],dp[to],lim);
    }
    dp[now][sz[now]]=dp[now][sz[now]-1];
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=*fac=iiv[1]=1;i<=n;++i)fac[i]=(LL)fac[i-1]*i%md;
    for(int i=2;i<=n;++i)iiv[i]=(md-md/i)*(LL)iiv[md%i]%md;
    for(int i=*iv=1;i<=n;++i)iv[i]=(LL)iv[i-1]*iiv[i]%md;
    while(m--){
        int u,v;
        scanf("%d%d",&u,&v);
        ++deg[u],++deg[v];
        G[u].push_back(v),G[v].push_back(u);
    }
    memset(inc,1,sizeof inc);
    topo();
    *s=1;
    for(int i=1;i<=n;++i)
    if(!inc[i]&&!root[i]&&getflag(i)){
        DfS(i,i);
        dfs(i,0);
        merge(s,s,dp[i],n);
    }
    for(int i=1;i<=n;++i)
    if(!inc[i]&&!flag[i]){
        if(!root[i])DfS(i,i),size[i]=sz[i];else dFs(i,root[i],0);
        dfs(i,0);
        for(int j=0;j<=size[root[i]];++j)
        upd(g[root[i]][j]+=dp[i][j]-md);
    }
    for(int i=1;i<=n;++i)
    if(!inc[i]&&!flag[i]&&root[i]==i){
        int*F=g[i];
        for(int j=0;j<size[i];++j)
        F[j]=(LL)F[j]*iiv[size[i]-j]%md;
        merge(s,s,F,n);
    }
    for(int i=0;i<=n;++i)printf("%d\n",s[i]);
    return 0;
}