Codeforces 512D. Fox And Travelling 题解
题目大意:
- 给定一张 \(n\) 个点 \(m\) 条边的无向图。
- 一个点只有当与它直接相连的点中最多只有一个点未被遍历过时才可被遍历。
- 询问对于每个 \(k \in [0,n]\),遍历 \(k\) 个点的方案数。
- \(n \le 100\),\(m \le \frac{n(n-1)}{2}\),答案对\(10^9+9\)取模。
(翻译来源:洛谷)
题解:首先,我们很明显可以对于每一个连通块单独考虑。
我们先假设这个连通块是一个有根树(即根若要删除的话必须是最后一个删掉)。
那么可以做一个树形DP,令\(f_{i,j}\)表示以节点\(i\)为根的子树中,选择\(j\)个节点的方案数。
对于每一个节点,做一遍背包再乘一个组合数就可以了。
再对每一个连通块分情况讨论:
- 这个连通块是一棵树,考虑计算从这棵树中选择\(k\)个节点的方案数。以每一个节点为根做一遍,加在一起。肯定会算重复,假设这棵有根树的大小为\(size\),接下来分两种情况:
- 选择的个数\(k<size\),由于根一定不会被选,所以选择的\(k\)个节点作为根时一定不会出现这种情况,然而另外\(size-k\)种肯定都计算了一遍,除掉就可以了。
- 选择的个数\(k=size\),因为以每一个点为根时最后删掉的一个节点一定不同,所以不会重复,不用处理就可以了。
- 如果这个连通块不是一棵树,那么至少存在一个环,环上的节点显而易见,肯定不能选,两个及两个以上的环中间夹着的节点也肯定不能选,那么用拓扑把所有能选的节点处理出来,这样会形成一个森林,然而有些节点因为已经和环连了边,所以如果要删掉的话就必须将它的子树全部删掉,也就是说,它就是有根树的根,那么直接跑一遍就没有问题了。
最后,把所有的状态再背包一下,背包时乘一个组合数就结束了。
下面是代码:
#include <cstdio>
#include <cstring>
const int Maxn=100;
const int Mod=1000000009;
int quick_power(int a,int b){
int ans=1;
while(b){
if(b&1){
ans=1ll*ans*a%Mod;
}
b>>=1;
a=1ll*a*a%Mod;
}
return ans;
}
int n,m;
int C[Maxn+5][Maxn+5];
void init(){
C[0][0]=1;
for(int i=1;i<=Maxn;i++){
C[i][0]=C[i][i]=1;
for(int j=1;j<i;j++){
C[i][j]=(C[i-1][j]+C[i-1][j-1])%Mod;
}
}
}
int head[Maxn+5],arrive[Maxn*Maxn*2+5],nxt[Maxn*Maxn*2+5],tot;
int deg[Maxn+5];
void add_edge(int from,int to){
arrive[++tot]=to;
nxt[tot]=head[from];
head[from]=tot;
}
int col[Maxn+5],col_tot;
bool un_vis[Maxn+5];
bool vis[Maxn+5],in[Maxn+5];
int que[Maxn+5],que_f,que_t;
void work_1(int S){
memset(in,0,sizeof in);
que_f=1;
que_t=0;
que[++que_t]=S;
in[S]=1;
while(que_f<=que_t){
int u=que[que_f++];
for(int i=head[u];i;i=nxt[i]){
int v=arrive[i];
if(in[v]){
continue;
}
in[v]=1;
que[++que_t]=v;
}
}
que_f=1,que_t=0;
for(int i=1;i<=n;i++){
if(in[i]&°[i]<=1){
que[++que_t]=i;
}
}
while(que_f<=que_t){
int u=que[que_f++];
vis[u]=1;
for(int i=head[u];i;i=nxt[i]){
int v=arrive[i];
deg[v]--;
if(deg[v]==1){
que[++que_t]=v;
}
}
}
for(int i=1;i<=n;i++){
if(in[i]&&(!vis[i])){
vis[i]=1;
un_vis[i]=1;
}
}
}
int sz[Maxn+5];
int f[Maxn+5][Maxn+5];
void work_2(int u,int fa){
memset(f[u],0,sizeof f[u]);
f[u][0]=1;
sz[u]=0;
for(int i=head[u];i;i=nxt[i]){
int v=arrive[i];
if(v==fa||un_vis[v]){
continue;
}
work_2(v,u);
for(int j=sz[u];j>=0;j--){
for(int k=sz[v];k>0;k--){
f[u][j+k]=(f[u][j+k]+1ll*f[u][j]*f[v][k]%Mod*C[j+k][k]%Mod)%Mod;
}
}
sz[u]+=sz[v];
}
f[u][sz[u]+1]=f[u][sz[u]];
sz[u]++;
}
int locked[Maxn+5];
bool m_r[Maxn+5];//must be the root
void work_3(int S,int c){
que_f=1,que_t=0;
que[++que_t]=S;
while(que_f<=que_t){
int u=que[que_f++];
col[u]=c;
for(int i=head[u];i;i=nxt[i]){
int v=arrive[i];
if(un_vis[v]){
locked[c]=u;
m_r[u]=1;
continue;
}
if(col[v]){
continue;
}
que[++que_t]=v;
}
}
}
int g[Maxn+5][Maxn+5];
int ans[Maxn+5];
int main(){
init();
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int u,v;
scanf("%d%d",&u,&v);
deg[u]++,deg[v]++;
add_edge(u,v);
add_edge(v,u);
}
for(int i=1;i<=n;i++){
if(!vis[i]){
work_1(i);
}
}
memset(f,-1,sizeof f);
ans[0]=1;
for(int i=1;i<=n;i++){
if(!un_vis[i]&&col[i]==0){
work_3(i,++col_tot);
if(locked[col_tot]){
work_2(locked[col_tot],0);
for(int j=0;j<=n;j++){
g[col_tot][j]=f[locked[col_tot]][j];
}
}
else{
int num=0;
for(int j=1;j<=n;j++){
if(col[j]==col_tot){
num++;
work_2(j,0);
for(int k=0;k<=sz[j];k++){
g[col_tot][k]=(g[col_tot][k]+f[j][k])%Mod;
}
}
}
for(int j=0;j<num;j++){
g[col_tot][j]=1ll*g[col_tot][j]*quick_power(num-j,Mod-2)%Mod;
}
}
for(int k=n;k>0;k--){
for(int j=k;j>0;j--){
ans[k]=(ans[k]+1ll*ans[k-j]*g[col_tot][j]%Mod*C[k][j]%Mod)%Mod;
}
}
}
}
for(int i=0;i<=n;i++){
printf("%d\n",ans[i]);
}
return 0;
}
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。