[NOI2007]生成树计数

题意

一个n个点的树,点i只能向[i−k,i−1]内的点连边,求有标号生成树的个数

然而做法,好吧我也不知道这是什么做法,所以有些注释大部分注释所有注释我都写在了代码里面,大佬们就自己去看看吧

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define RG register
#define MOD 65521
#define MAX 55
inline ll read()
{
    RG ll x=0,t=1;RG char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return x*t;
}
ll n;int K,cnt;
int p[1<<20],st[MAX];
struct Matrix
{
    int s[MAX][MAX];
    void clear(){memset(s,0,sizeof(s));}
    void init(){clear();for(int i=1;i<=cnt;++i)s[i][i]=1;}
}G;
Matrix operator*(Matrix a,Matrix b)
{
    Matrix ret;ret.clear();
    for(int i=1;i<=cnt;++i)
        for(int j=1;j<=cnt;++j)
            for(int k=1;k<=cnt;++k)
                ret.s[i][j]=(ret.s[i][j]+1ll*a.s[i][k]*b.s[k][j])%MOD;
    return ret;
}
Matrix fpow(Matrix a,ll b)
{
    Matrix s;s.init();
    while(b){if(b&1)s=s*a;a=a*a;b>>=1;}
    return s;
}
bool check(int t)//检查一个状态是否合法
{
    int tmp=1<<1;//因为第一个点一定属于一号联通快,所以先把一号联通快放进去检查
    for(int i=3;i<K+K+K;i+=3)
    {
        for(int j=1;j<((t>>i)&7);++j)//检查比当前编号小的所有编号是否都已经出现过
            if(!(tmp&(1<<j)))return false;
        tmp|=1<<((t>>i)&7);//将当前编号也给放进来
    }
    return true;
}
void dfs(int x,int t)//暴力找出所有状态,每个编号利用3个二进制位存
{
    if(x==K){if(check(t))p[t]=++cnt,st[cnt]=t;return;}
    for(int i=1;i<=K;++i)dfs(x+1,t|(i<<(x+x+x)));
}
int fa[MAX],a[MAX];
int getf(int x){return x==fa[x]?x:fa[x]=getf(fa[x]);}
int f[MAX],g[MAX][MAX];
int main()
{
    K=read();n=read();dfs(1,1);
    for(int i=1;i<=cnt;++i)
    {
        f[i]=1;memset(a,0,sizeof(a));
        for(int j=0;j<K;++j)++a[(st[i]>>(j*3))&7];
        for(int j=1;j<=K;++j)
            if(a[j]==3)f[i]=3;
            else if(a[j]==4)f[i]=16;
            else if(a[j]==5)f[i]=125;
        int t=st[i];
        for(int s=0;s<(1<<K);++s)//暴力枚举当前点对于前面几个点的连边状态
        {
            for(int j=0;j<=K;++j)fa[j]=j;
            for(int j=0;j<K;++j)//利用并查集维护联通性
                for(int k=j+1;k<K;++k)
                    if(((t>>(3*j))&7)==((t>>(3*k))&7))
                        fa[getf(j)]=getf(k);
            bool cir=false;
            for(int j=0;j<K;++j)//检查当前点的连边
                if(s&(1<<j))
                {
                    if(getf(K)==getf(j)){cir=true;break;}//出现了环
                    fa[getf(K)]=getf(j);
                }
            if(cir)continue;//连边不合法
            for(int j=1;j<=K;++j)//最前面的点必须和后面的一个点联通,否则就无法联通了
                if(getf(0)==getf(j)){cir=true;break;}
            if(!cir)continue;
            int now=0,used=0;
            for(int j=0;j<K;++j)//当前存在合法的联通方案,因此当前的状态可以转移到另外的一个状态上去
                if(!(now&(7<<(j*3))))//当前点不在任意一个联通块中
                {
                    now|=++used<<(j*3);//新的联通块
                    for(int k=j+1;k<K;++k)//把所有在一个联通块里的点丢到状态里去
                        if(getf(j+1)==getf(k+1))
                            now|=used<<(k*3);
                }
            g[i][p[now]]++;
        }
    }
    for(int i=1;i<=cnt;++i)
        for(int j=1;j<=cnt;++j)
            G.s[i][j]=g[i][j];
    G=fpow(G,n-K);
    int ans=0;
    for(int i=1;i<=cnt;++i)
        ans=(ans+1ll*G.s[i][1]*f[i])%MOD;
    printf("%d
",ans);
    return 0;
}

如果有问题的话大佬们可以指出

原文地址:https://www.cnblogs.com/zi-nai-boboyang/p/11437178.html