[航海协会]树

题目概述

在这里插入图片描述

题解

首先看到这个题,我们应该比较容易得到一个状压的做法。
就是我们定义 d p i , j dp_{i,j} dpi,j表示节点 i i i m e x \rm mex mex j j j的方案数。
显然,一个点的权值最后是极其有限的,显然它是不能够超过它的出度的。
那我们就可以考虑每次状压记录当前节点的已加入儿子已经存在哪些不超过度数上限的权值,最后把最小的没有加入的权值找到就行了。
但这样可能会出现一种情况,就是类似菊花图的,度数很多的情况,那不炸麻。

我们不妨猜测一下,如果它的度数很多,这个点的上限不会太高。
但好像不是这样的,因为它可能既有非叶节点的儿子也有叶节点的儿子,这两者加在一起,是非常恐怖的。
不过叶节点这东西好搞,它为每个权值的情况数都是 1 1 1
我们容易通过这个性质再想到一个新的状压做法,记录 g i , S , j g_{i,S,j} gi,S,j表示我们使用 S S S集合中的非叶节点与 j j j个叶节点,填充了 [ 0 , i ] [0,i] [0,i]的所有位置。
这样的话,我们只要转移到某一位的时候,没有往里面填任何的数,那么它的权值就是这个了,把剩下的没填的数随便填到更大的数里面即可。
但如果我们非叶节点的儿子很多呢?
没关系,在这些儿子中上限高的肯定很少。
我们可以再给他套上一个根号分治,先对于上限小的儿子,采用我们上面的方法,状压得到小范围的每个选择状态的方案数。
然后再对这些方案每个用我们第二个状压方法转移一次。
这样就能够求出所有的 d p i , j dp_{i,j} dpi,j了。

时间复杂度 O ( 2 2 n n 2 ) O\left(2^{\sqrt{2n}}n^2\right) O(22n n2)

源码

#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int> pii;
#define MAXM (1<<15)+5
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
#define lson (rt<<1)
#define rson (rt<<1|1)
const int INF=0x3f3f3f3f;
const int mo=998244353;
template<typename _T>
void read(_T &x){
    _T f=1;x=0;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
    x*=f;
}
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
int add(int x,int y,int p){return x+y<p?x+y:x+y-p;}
void Add(int &x,int y,int p){x=add(x,y,p);}
int qkpow(int a,int s,int p){int t=1;while(s){if(s&1)t=1ll*a*t%p;a=1ll*a*a%p;s>>=1;}return t;}
int n,head[205],tot,deg[205],dp[205][205],up[205],sta[205],stak;
int g[2][MAXM],h[MAXM][205],fac[205],inv[205],ff[205],suf[205][205];
int S[205][205],C[205][205],tmp[2][MAXM][205][2],gp[205];bool leaf[205];
struct edge{int to,nxt;}e[405];
void addEdge(int u,int v){e[++tot]=(edge){v,head[u]};head[u]=tot;}
bool cmp(int x,int y){return up[x]<up[y];}
void dosaka(int u,int fa){
    for(int i=head[u];i;i=e[i].nxt)
        if(e[i].to^fa)dosaka(e[i].to,u);
    stak=0;int num=0;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;if(v==fa)continue;
        deg[u]++;num+=(leaf[v]);
        if(!leaf[v])sta[++stak]=v;
    }
    if(!deg[u]){leaf[u]=1;return ;}
    sort(sta+1,sta+stak+1,cmp);int mid=0,minn=stak,nw=0;
    for(int i=1;i<=stak;i++)nw=min(nw+1,up[sta[i]]+1);up[u]=nw+num;
    for(int i=1;i<=stak;i++)
        if(up[sta[i]]+stak-i<minn)
            minn=up[sta[i]]+stak-i,mid=i;
    int upp=up[sta[mid]],lim=(1<<upp+1),lm=(1<<stak-mid),now=0,las=1;
    for(int i=0;i<lim;i++)g[0][i]=g[1][i]=0;g[now][0]=1;
    for(int i=1;i<=mid;i++){
        swap(now,las);int v=sta[i];
        for(int j=0;j<=up[v];j++)if(dp[v][j])
            for(int k=0;k<lim;k++)
                Add(g[now][k|(1<<j)],1ll*dp[v][j]*g[las][k]%mo,mo);
        for(int k=0;k<lim;k++)g[las][k]=0;
    }
    for(int i=0;i<lim;i++)g[0][i]=g[now][i];
    for(int i=0;i<lim;i++){
        const int tp=g[0][i];if(!tp)continue;h[0][0]=tp;
        for(int j=0;j<=up[u];j++){
            now=0,las=1;
            for(int k1=0;k1<lm;k1++)
                for(int k2=0;k2<=min(num,j);k2++)
                    tmp[now][k1][k2][0]=h[k1][k2],h[k1][k2]=0;
            for(int l=mid+1;l<=stak;l++){
                int v=sta[l],gg=(1<<l-mid-1);if(up[v]<j)continue;swap(now,las);
                for(int k1=0;k1<lm;k1++)
                    for(int k2=0;k2<=min(num,j);k2++)
                        for(int k3=0;k3<2;k3++)if(tmp[las][k1][k2][k3]){
                            if(!(k1&gg))Add(tmp[now][k1|gg][k2][1],1ll*tmp[las][k1][k2][k3]*dp[v][j]%mo,mo);
                            Add(tmp[now][k1][k2][k3],tmp[las][k1][k2][k3],mo);tmp[las][k1][k2][k3]=0;
                        }
            }
            for(int k1=0;k1<lm;k1++)
                for(int k2=0;k2<=min(num,j);k2++){
                    Add(h[k1][k2],tmp[now][k1][k2][1],mo);
                    if(k2<num)Add(h[k1][k2+1],tmp[now][k1][k2][1],mo),
                        Add(h[k1][k2+1],tmp[now][k1][k2][0],mo);
                    int rs=tmp[now][k1][k2][0];tmp[now][k1][k2][0]=tmp[now][k1][k2][1]=0;
                    if(!rs)continue;if(j<=upp&&(i&(1<<j))){Add(h[k1][k2],rs,mo);continue;}
                    for(int k3=mid+1;k3<=stak;k3++)if(!((k1>>k3-mid-1)&1))
                        rs=1ll*suf[sta[k3]][j+1]*rs%mo;
                    Add(gp[k2],rs,mo);
                }
            for(int k1=0;k1<=min(num,j);k1++)if(gp[k1]){
                for(int k2=0;k2<=n-j;k2++)
                    Add(dp[u][j],1ll*S[num][k1+k2]*fac[k1+k2]%mo*C[n-j][k2]%mo*gp[k1]%mo,mo);
                gp[k1]=0;
            }
        }
    }
    for(int i=up[u];i>=0;i--)suf[u][i]=add(suf[u][i+1],dp[u][i],mo);
    for(int i=0;i<lim;i++)g[0][i]=0;
}
void init(){
    fac[0]=fac[1]=inv[0]=inv[1]=ff[1]=1;
    for(int i=2;i<=n;i++)
        fac[i]=1ll*i*fac[i-1]%mo,
        ff[i]=1ll*(mo-mo/i)*ff[mo%i]%mo,
        inv[i]=1ll*ff[i]*inv[i-1]%mo;
    for(int i=0;i<=n;i++){
        C[i][0]=C[i][i]=S[i][i]=1;
        for(int j=1;j<i;j++)
            C[i][j]=add(C[i-1][j-1],C[i-1][j],mo),
            S[i][j]=add(1ll*j*S[i-1][j]%mo,S[i-1][j-1],mo);
    }
}
int main(){
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    read(n);init();
    for(int i=1,u,v;i<n;i++)
        read(u),read(v),addEdge(u,v),addEdge(v,u);
    dosaka(1,0);
    for(int i=0;i<=n;i++)printf("%d\n",dp[1][i]);
    return 0;
}

谢谢!!!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值