BZOJ 1494: [NOI2007]生成树计数

这题确实比较难,

所以我写解题报告也尽量从浅显处着手,希望能够帮到大家。


题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1494


题目大意:有n(n<=10^15)个点,序号1~n,只有序号相差不超过k(k<=5)的点之间才可以连边,问生成树的种数。


算法:


从这道题的数据范围,不难看出要使用状态压缩。


首先,我们来确定前k个点的状态。

原本想用一个k位二进制数记录每个点跟它前面的k个点的连接情况,

但是这样处理不了一个问题,就是如何排除环的存在。

所以借鉴了一些解题报告,发现可以在由1~n依次处理节点时,

用一个k位k进制数来记录最近k个点所在的联通块的情况,

这样的状态其实并不多,n=5的时候有52个。


为了避免状态的冗余,就要保证一个状态只有一种表示形式,

为此我学习了非常巧妙的“最小表示法”(SOJ 3296)。

但其实本题与最小表示法并无关系,

最小表示法求的是一个串的字典序最小的循环表示。

而本题中显然不能对状态进行循环表示,

而只能在已知哪几个节点处于同一个联通块的情况下,改变联通块的标号,使得得到的状态最小。


当我们枚举出了初始的状态后,例如节点1,2,3属于联通块0,节点4,5属于联通块1。

那么,同一个联通块中,生成树的形态仍然有很多。

在后面的状态转移的过程中,我们可以通过枚举边来自然的进行转移。

但是初始状态却没办法这样,对于一个初始状态,我们无法得知有多少种连边方式可以得到它。

本题给出了一个利用矩阵计算生成树个数(HDOJ 4305)的方法,具体可以参考周冬的《生成树的计数及其应用》


对于有n个点的图,构造一个矩阵,使得:

若i==j,a[i][j]=dex[i]。(dex[i]为节点i的度数)。

否则,若点i和点j间有边相连,a[i][j]=-1。

若无边相连,a[i][j]=0。

然后求这个矩阵任何一个n-1阶主子式的行列式的绝对值,得到的即是这个图的生成树个数。


当然,如果计算的行列式的值非常大,自然就要对某个数取模(本题中K<=5,没有这个必要)。

对于行列式模任意值的解法,bjin的论文《欧几里得算法的应用》中有介绍。

另外,个人觉得bjin的两道题: SPOJ DETER3(行列式模) 和 SPOJ MSTS(最小生成树计数,HDOJ 4408与此题类似),

非常适合作为本题的后续练习。


显然,对于这道题。

假设相邻K个点中,有x个点同属于1号联通块

那么这x个点中两两间都必然是可以连边的。

我们要求的是x个点的完全图的生成树种数,

得到的即是联通块1在当前的K个点这个部分,所能得到的生成树种数。

当然,其实不必真的去计算行列式,题目的开头已经告诉我们了,n个节点的完全图的生成树个数是n^(n-2)。

这个结论可以用cayley定理来证明。


处理了初始状态之后,再来看一下状态的转移。

从序号由小到大处理各节点。

每处理一个节点x时,用一个k位二进制数枚举这个点跟它前面的k个点的连接情况,

然后就可以由[x-k-1,x-1]的原状态转化到[x-k,x]的新状态。

当然,这过程中可能出现环,这个可以用并查集来判断,如果出现环,这个转移就是无效的。

显然,按节点顺序从小到大处理,每次在标记一个新的联通块的时候,使用当前未使用的最小标号即可。


我们不难看出,状态转移时的倍数关系,只与原状态和新状态有关,跟点的序号无关。

因此我们可以构造一个初始向量和一个转移矩阵。

我构造的初始向量g[]是一个行向量。每个位置代表一种初始状态。

每个位置的值,就是前k个点有多少种连接方式能够达成这种状态。

然后对于状态转移矩阵f[][],第i行第j列表示由状态i转移到状态j有多少种合法的连接方式(不成环且第x-k个点至少与x-k+1~x中的一个点同一个联通块)。


求出g*pow(f,n-k)之后,对于得到的行向量中每个位置的数值,就代表了有多少种达到这个状态的方法。

然而,这些状态并不都是有用的。

只有所有点处于同一个联通快的那个状态是有用的,也就是状态0。

所以输出g[0][0]即可。


PS:

做这道题的过程中主要参考了俞华程的论文《矩阵乘法在信息学中的应用》和陈丹琦的论文《基于连通性状态压缩的动态规划问题》(恰好以上两位也是OI界有名的赛场伉俪,有木有有木有!),以及ACMonsterwhjpji的解题报告。

非常感谢!

还要谢谢适牛借给我他超级长长长长长长长长长长的矩阵类模板哟~~~~~~~~


代码如下:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<sstream>
#include<cstdlib>
#include<cstring>
#include<string>
#include<climits>
#include<cmath>
#include<queue>
#include<vector>
#include<stack>
#include<set>
#include<map>
#define INF 0x3f3f3f3f
#define eps 1e-8
using namespace std;

const long long MOD = 65521;
const int MAXK=5,MAXS=60;
int status[MAXS],hash[1<<(3*MAXK)];
int p[MAXK+1],cot[MAXK];
int val[6]= {1,1,1,3,16,125};
long long n;
int k;
int tot;

struct Matrix
{
    int h,w;
    long long mx[MAXS][MAXS];

    Matrix()
    {
        h=0;
        w=0;
        memset(mx,0,sizeof(mx));
    }
    Matrix operator* (const Matrix& b) const
    {
        Matrix tmp;
        memset(tmp.mx,0,sizeof(tmp.mx));
        tmp.h=h;
        tmp.w=b.w;
        for (int i=0; i<h; i++)
        {
            for (int j=0; j<b.w; j++)
            {
                for (int k=0; k<w; k++)
                {
                    tmp.mx[i][j]=(tmp.mx[i][j]+(mx[i][k]*b.mx[k][j])%MOD)%MOD;
                }
            }
        }
        return tmp;
    }

    void initE()
    {
        memset(mx,0, sizeof(mx));
        for (int i=0 ; i<w ; i++)
        {
            mx[i][i]=1LL;
        }
    }

    Matrix mpow(long long  k)
    {
        Matrix c,b;
        c=(*this);
        memset(b.mx,0,sizeof(b.mx));
        b.w=w;
        b.h=h;
        b.initE();
        while(k)
        {
            if(k&1LL)
            {
                b=b*c;
            }
            c=c*c;
            k>>=1LL;
        }
        return b;
    }
};
Matrix g,f;

void dfs(int mask, int dep)
{
    if(dep==k)
    {
        g.mx[0][tot]=1;
        memset(cot,0,sizeof(cot));
        for(int i=0; i<k; i++)
        {
            cot[mask>>(i*3)&7]++;
        }
        for(int i=0; i<k; i++)
        {
            g.mx[0][tot]*=val[cot[i]];
        }
        status[tot]=mask;
        hash[mask]=tot++;
        return;
    }
    int tmp=-1;
    for(int i=0; i<dep; i++)
    {
        tmp=max(tmp,mask>>(i*3)&7);
    }
    for(int i=0; i<=tmp+1&&i<k; i++)
    {
        dfs(mask<<3|i,dep+1);
    }
}

int findp(int x)
{
    return p[x]==-1?x:p[x]=findp(p[x]);
}

int justify()
{
    bool vis[MAXK];
    memset(vis,0,sizeof(vis));
    int tot=0;
    int mask=0;
    for(int i=k-1; i>=0; i--)
    {
        if(!vis[i])
        {
            vis[i]=true;
            mask|=tot<<(i*3);
            for(int j=i-1; j>=0; j--)
            {
                if(findp(i+1)==findp(j+1))
                {
                    vis[j]=true;
                    mask|=tot<<(j*3);
                }
            }
            tot++;
        }
    }
    return hash[mask];
}

void cal(int s, int mask)
{
    memset(p,-1,sizeof(p));
    for(int i=0; i<k; i++)
    {
        for(int j=i+1; j<k; j++)
        {
            if((status[s]>>(i*3)&7)==(status[s]>>(j*3)&7))
            {
                int px=findp(i);
                int py=findp(j);
                if(px!=py)
                {
                    p[px]=py;
                }
            }
        }
    }
    for(int i=0; i<k; i++)
    {
        if((mask>>i)&1)
        {
            int px=findp(i);
            int py=findp(k);
            if(px==py)
            {
                return;
            }
            p[px]=py;
        }
    }
    bool flg=false;
    for(int i=1; i<=k; i++)
    {
        if(findp(i)==findp(0))
        {
            flg=true;
            break;
        }
    }
    if(!flg)
    {
        return;
    }
    f.mx[s][justify()]++;
}

int main()
{
    while(scanf("%d%lld",&k,&n)==2)
    {
        memset(f.mx,0,sizeof(f.mx));
        memset(g.mx,0,sizeof(g.mx));
        tot=0;
        dfs(0,0);
        g.h=1;
        g.w=f.w=f.h=tot;
        for(int i=0; i<tot; i++)
        {
            for(int mask=0; mask<(1<<k); mask++)
            {
                cal(i,mask);
            }
        }
        g=g*f.mpow(n-k);
        printf("%lld\n",g.mx[0][0]);
    }
    return 0;
}


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值