CF512D Fox And Travelling

第一篇博客
IOI2020集训队作业
首先我们考虑环中的点一定不会被遍历,然后我们就可以把环中点扔掉
紧接着我们有一些有根树和无根树,有根树就是树中唯一和环相连的,因为这样的点只能在在最后选
那么有根树上怎么树上背包呢?
相当于我们有个长度为j+k的序列向里面放一个长度为j的序列和一个长度为k的序列,肯定先硬点j个位置
无根树?枚举那个点做根然后有根树dp
最后不同的数背包+组合数合起来
注意无根树中一个j个点方案会被多算siz-j次
code:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=107;
const int P=1e9+9;
int n,m,d[MAXN],w[MAXN],b[MAXN],s[MAXN],S;
vector<int> e[MAXN];
ll p[MAXN],v[MAXN],vp[MAXN],f[MAXN][MAXN],ans[MAXN];
inline int read() {
int x=0,f=1;
char s=getchar();
for(; !isdigit(s); s=getchar());
for(; isdigit(s); s=getchar())x=(x<<1)+(x<<3)+s-'0';
return x;
}
inline ll C(int a,int b) {
return p[a]*vp[b]%P*vp[a-b]%P;
}
void dfs(int x,int o,int &s) {
b[x]=o,++s;//拉出每个点所属的跟
for(unsigned int i=0; i<e[x].size(); ++i) {
int y=e[x][i];
if(!d[y]&&!b[y])dfs(y,o,s);
}
return ;
}
inline void dp(int x,int fa) {
s[x]=1,f[x][0]=1;
for(unsigned int i=0; i<e[x].size(); ++i) {
int y=e[x][i];
if(b[x]!=b[y]||y==fa)continue;
dp(y,x);
for(int j=0; j<s[y]; ++j)f[x][s[x]+j]=0;
for(int j=s[x]-1; ~j; --j) {//注意这里要减1
for(int k=1; k<=s[y]; ++k) {
f[x][j+k]=(f[x][j+k]+f[x][j]*f[y][k]%P*(C(j+k,j))%P)%P;
//同一棵树上相当于dp了顺序
}
}
s[x]+=s[y];//siz的意思
}
f[x][s[x]]=f[x][s[x]-1];//只能这么转移了
}
inline void get(int x) {
dp(x,0);
for(int i=0; i<=s[x]; ++i)f[0][i]=(f[0][i]+f[x][i])%P;
//卷一下
}
inline ll ksm(ll x,int y) {
ll ans=1;
while(y) {
if(y&1)ans=ans*x%P;
x=x*x%P;
y>>=1;
}
return ans;
}
int main() {
n=read();
m=read();
p[0]=v[0]=1;
for(int i=1; i<=n; ++i)p[i]=p[i-1]*i%P;
vp[n]=ksm(p[n],P-2);
for(int i=n; i; --i)v[i]=p[i-1]*vp[i]%P,vp[i-1]=vp[i]*i%P;
//三种逆元?
//阶乘,逆阶乘逆元,单数
for(int i=1,x,y; i<=m; ++i)x=read(),y=read(),e[x].push_back(y),e[y].push_back(x),++d[x],++d[y];
queue<int> q;
for(int i=1; i<=n; ++i)if(d[i]<=1)w[i]=1,q.push(i);
//叶子结点!w是vis数组
while(q.size()) {
int x=q.front();
q.pop();
for(unsigned int i=0; i<e[x].size(); ++i) {
int y=e[x][i];
if(--d[y]<=1&&!w[y])w[y]=1,q.push(y);//当前的叶子节点
}
}
for(int i=1; i<=n; ++i)
if(d[i] == 1)dfs(i,i,s[i]);//从这些点出发dfs,统计大小和父亲
for(int i=1; i<=n; ++i)if(!d[i]&&!b[i])dfs(i,i,s[i]);//我不联通的呢
ans[0]=1;
for(int i=1; i<=n; ++i)
if(i==b[i]) {
int o=s[i];
if(d[i]==1)get(i);
else {
for(int j=1; j<=n; ++j)
if(b[j]==i)get(j);//遍历这整棵树做根
for(int j=0; j<=o; ++j)f[0][j]=f[0][j]*v[o-j]%P;
//j个点的方案会被多算o-j次,因为在枚举o-j剩下的点做根的时候又会算一遍
}
for(int j=S; ~j; --j) {
for(int k=1; k<=o; ++k) {
ans[j+k]=(ans[j+k]+ans[j]*f[0][k]%P*C(j+k,j)%P)%P;//0一定要走了
}
}
//这里背包一下
for(int j=0; j<=o; ++j)f[0][j]=0;
//清空一下
S+=o;
}
for(int i=0; i<=n; ++i)printf("%d\n",ans[i]);
return 0;
}