这题确实比较难,
所以我写解题报告也尽量从浅显处着手,希望能够帮到大家。
题目链接: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个。
为了避免状态的冗余,就要保证一个状态只有一种表示形式,
但其实本题与最小表示法并无关系,
最小表示法求的是一个串的字典序最小的循环表示。
而本题中显然不能对状态进行循环表示,
而只能在已知哪几个节点处于同一个联通块的情况下,改变联通块的标号,使得得到的状态最小。
当我们枚举出了初始的状态后,例如节点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界有名的赛场伉俪,有木有有木有!),以及ACMonster和whjpji的解题报告。
非常感谢!还要谢谢适牛借给我他超级长长长长长长长长长长的矩阵类模板哟~~~~~~~~
代码如下:
#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;
}