这题确实比较难,
所以我写解题报告也尽量从浅显处着手,希望能够帮到大家。
题目链接: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界有名的赛场伉俪,有木有有木有!),以及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; }