bzoj1494【Noi2007】生成树计数

题意:http://www.lydsy.com/JudgeOnline/problem.php?id=1494

sol  :前排膜拜http://blog.csdn.net/qpswwww/article/details/45362639

   虽然dalao们说了一些最小表示法啊什么k=5时只有52种状态啊balabala,然而我并不会.......

 

   因为k很小,可以考虑用状压来记录联通块信息,考虑dp

   dp[i][j]表示前i个点,最后k个点联通情况为j时的方案数

   由于n很大,考虑采用矩阵快速幂优化,用并查集判环&维护

   那么最终可以构造函数f[x][y]表示状态x向状态y转移有多少种合法的连接方式

   初始函数g[x]是一个行向量,每个位置代表一个初始状态

   这样就可以做了........最后输出A[1][1]即可

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
const int Mx=200;
const int p=65521;
using namespace std;
ll n;
int K,tot/*状态总数*/,Tr_siz[]={1,1,1,3,16,125}/*完全图的生成树个数*/;
int fa[Mx],siz[Mx]/*第i个联通块的大小*/,status[600],hash[1<<16]/*hash[S]=状态S的编号*/;

struct Matrix
{
    int n,m; ll num[Mx][Mx];
    Matrix() { n=m=0; memset(num,0,sizeof(num)); }
}A,trans;
Matrix operator*(Matrix a,Matrix b)
{
    Matrix c;
    c.n=a.n,c.m=b.m;
    for(int k=1;k<=a.m;k++)
        for(int i=1;i<=c.n;i++)
            for(int j=1;j<=c.m;j++)
                c.num[i][j]=(c.num[i][j]+a.num[i][k]*b.num[k][j]%p)%p;
    return c;
}
void Pow(ll c)
{
    while(c)
    {
        if(c&1) A=A*trans;
        trans=trans*trans;
        c>>=1;
    }
}

void dfs(int x,int sta) //当前要加入第x个点的联通状态,当前的状态为sta
{
    if(x==K+1)
    {
        memset(siz,0,sizeof(siz));
        A.num[1][++tot]=1;
        for(int i=1;i<=K;i++)
            siz[sta>>((i-1)*3)&7]++;
        for(int i=0;i<K;i++)
            A.num[1][tot]*=Tr_siz[siz[i]];
        status[tot]=sta;
        hash[sta]=tot;
        return;
    }
    int tmp=-1; //联通块的最大编号,联通块编号的区间是[0,K-1]
    for(int i=1;i<x;i++) //!!!当前的sta里只保存了1~pos-1这些点的连通性
        tmp=max(tmp,sta>>((i-1)*3)&7);
    for(int i=0;i<=tmp+1&&i<K;i++)
        dfs(x+1,sta<<3|i);
}

int find(int x) 
{
    if(fa[x]==x) return x;
    return fa[x]=find(fa[x]);
}

int Get_Status() //用当前的并查集来求出新的点2到点k+1的最小表示
{
    int sta=0,tot=0;
    bool vis[Mx]; memset(vis,false,sizeof(vis));
    for(int i=K+1;i>=2;i--)
        if(!vis[i])
        {
            vis[i]=true,sta|=tot<<((i-2)*3);
            for(int j=i-1;j>=2;j--)
                if(find(i)==find(j))
                    vis[j]=true,sta|=tot<<((j-2)*3);
            tot++;
        }
    return hash[sta];
}

void cal(int sta,int addsta) //用加边状态addsta去更新最小表示法sta,addsta里的第i位为1表示第k+1个点要和点i+1连新边
{
    for(int i=0;i<=K+1;i++) fa[i]=i;
    for(int i=1;i<=K;i++) //枚举点对(i,j)是否在最小表示法里的同一联通块内,将最小表示法中的连通性用并查集表示
        for(int j=i+1;j<=K;j++)
            if((status[sta]>>((i-1)*3)&7)==(status[sta]>>((j-1)*3)&7))
            {
                int rooti=find(i),rootj=find(j);
                if(rooti!=rootj) fa[rooti]=rootj;
            }
    for(int i=1;i<=K;i++)
        if(addsta&(1<<(i-1)))
        {
            int rooti=find(i),rootj=find(K+1);
            if(rooti==rootj) return; //判环,加的新边的两端点原来就是联通的,加入新边后会出现环
            fa[rooti]=rootj;
        }
    bool flag=false; //flag=true表示有点和点1联通
    for(int i=2;i<=K+1;i++)
        if(find(i)==find(1))
        {
            flag=true; break;
        }
    if(!flag) return; //点1不链接后面的点,那么这个生成树不联通
    trans.num[sta][Get_Status()]++;
}

int main()
{
    scanf("%d%lld",&K,&n);
    dfs(1,0); A.n=1; A.m=trans.n=trans.m=tot;
    for(int i=1;i<=tot;i++)
        for(int j=0;j<(1<<K);j++) cal(i,j);
    Pow(n-K); printf("%lld\n",A.num[1][1]);
    return 0;
}

 

posted @ 2017-03-29 02:38  Czarina  阅读(904)  评论(0编辑  收藏  举报