【NOI2007/BZOJ1494】生成树计数 插头DP

原题走这里

一看数据范围就知道是矩阵快速幂优化
首先,设当前点为 i i
接着就会发现,i不可能与 iK i − K 之前的点相连,
因此当前点的连通性只与 iK i − K i1 i − 1 共K个点的连通性有关。
于是我们可以用 d(i,S) d ( i , S ) 来表示当前点为 i i ,连通性为S的情况时的情况数。
使用最小表示法,能够把连通性实质上不同的情况优化到只剩五十几种。

最小表示法,说白了就是用标号表示点所属的连通块,并且编号按出现的顺序从0开始递增。
例如:如果1,2,3属于同一个连通块,4,6属于同一个连通块,5,7各属于一个连通块,用最小表示法就能表示成0001213。

接着是转移。转移方程可以大致写成是: d(i,S)=d(i1,Slast) d ( i , S ) = ∑ d ( i − 1 , S l a s t )
为了转移,我们可以让点 i i 与前K个点任意连边,但不能形成环。
此外还有一点:在只有 iK i − K 为0号连通块时, i i 必须与iK连边
(否则,0号连通块将会与其他连通块彻底分离,无法形成生成树)
于是乎,在连边的过程中,可能有一些连通块合并了,也可能 i i <script type="math/tex" id="MathJax-Element-5234">i</script>没有向任何点连边,自己形成了一个连通块,总之,连边后要重新将状态表示成最小表示法。

由于本DP的每一步转移都十(yi)分(mu)类(yi)似(yang),而且类似线性递推,因此可以用矩阵乘法优化,50*50*50毫无压力。

代码如下:

#include <bits/stdc++.h>
#define LL long long
#define MOD 65521  
using namespace std;
const int num[6]={1,1,1,3,16,125};
LL n,m,H[60],s,A[60],ans,d[6][60][6],code[6],cnt[6];
map<LL,int> IH;
struct Matrix
{
    LL a[60][60];
    Matrix()
    {
        memset(a,0,sizeof(a));
    }
    inline Matrix friend operator*(const Matrix &m1,const Matrix &m2)
    {
        Matrix m3;
        for(int i=0;i<s;i++)
        {
            for(int j=0;j<s;j++)
            {
                for(int k=0;k<s;k++)
                {
                    (m3.a[i][j]+=(m1.a[i][k]*m2.a[k][j]))%=MOD; 
                }   
            }   
        }
        return m3;  
    } 
}M;
void getHash(int k,int x,int y)//k层数,x当前允许最大编号,y当前哈希值 
{
    if(k==m)
    {
        IH[y]=s;
        H[s++]=y;
        return;
    }
    for(int i=0;i<=x;i++)
    {
        getHash(k+1,i==x?x+1:x,y|(i<<(3*k)));
    }
}
int recode(int &x,int t=0)
{
    memset(code,-1,sizeof(code));
    for(int i=0,j=0;i<m;i++)
    {
        int y=x>>(3*i)&7;
        if(code[y]==-1)
        {
            code[y]=j++;
        }
        x=x^(y<<3*i)^(code[y]<<3*i);
    }
    return code[t];
}
void dp()
{
    for(int i=0;i<m;i++)
    {
        for(int j=0;j<s;j++)
        {
            bool flag=(i==0);//急需拯救状态
            int t=H[j],x=t>>(3*i)&7;
            for(int k=1;k<m;k++)
            {
                if((t<<(3*k)&7)==0)flag=0;  
            } 
            if(flag)
            {
                (d[i+1][j][0]=d[i][j][m])%=MOD;
                continue;
            }
            for(int k=0;k<=m;k++)
            {
                (d[i+1][j][k]+=d[i][j][k])%=MOD;//不连线的状态 
                if(k==x)
                {
                    continue;//不可成环 
                }
                int tt=t,tk=k,tx=x;
                if(tx<tk)swap(tx,tk);
                for(int l=0;l<m;l++)
                {
                    if(tx==(t>>(3*l)&7))
                    {
                        tt=tt^(tx<<3*l)^(tk<<3*l);//连通块合并 
                    }
                }
                tk=recode(tt,tk);//合并后重编号 
                (d[i+1][IH[tt]][tk]+=d[i][j][k])%=MOD;//联线状态
            }
        }
    }
}
void getM()
{
    for(int i=0;i<s;i++)
    {
        memset(d,0,sizeof(d));
        d[0][i][m]=1;
        dp();
        for(int j=0;j<s;j++)
        {
            bool flag=1;
            for(int k=1;k<m;k++)
            {
                if((H[j]>>(3*k)&7)==0)flag=0;
            } 
            for(int k=0;k<=(flag?0:m);k++)
            {
                int t=H[j]>>3|(k<<(3*m-3));
                recode(t);
                M.a[i][IH[t]]+=d[m][j][k];
            }
        }
    }
}
Matrix qpow(Matrix x,LL y)
{
    Matrix ans;
    for(int i=0;i<s;i++)ans.a[i][i]=1;
    while(y)
    {
        if(y&1)ans=ans*x;
        x=x*x;
        y>>=1;
    }
    return ans;
}
void getans()
{
    for(int i=0;i<s;i++)
    {
        memset(cnt,0,sizeof(cnt));
        A[i]=1;
        for(int j=0;j<m;j++)
        {
            cnt[H[i]>>(3*j)&7]++;
        }
        for(int j=0;j<m;j++)
        {
            (A[i]*=num[cnt[j]])%=MOD;
        }
        (ans+=A[i]*M.a[i][0])%=MOD;
    }
}
int main()
{
    cin>>m>>n;  
    getHash(0,0,0);
    getM();
    M=qpow(M,n-m);
    getans();
    cout<<ans<<endl;
    return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值