count (類插頭DP+矩陣快速冪)
題目大意:有n個點,編號為1~n。第i個點和第j個點之間有一條無向邊當且僅當|i-j|<=k。求這個圖的生成樹個數。。
題目分析:Coming在他初二時的資料裡找到的一道題,是我校上古大神cdc給的。我不得不吐槽:難道前幾屆的dalao初二就能做這種題了嗎?而且題面還很惡意地給出了怎麼用矩陣樹定理算無向圖的生成樹個數……
言歸正傳,這題很明顯就是狀態壓縮,然後扔進矩乘。首先認為所有邊都是從編號大的點連向編號小的點。對於第i個點,用狀壓記錄包括它在內前面k個點的連通塊歸屬情況。由於用最小表示法記錄(最小表示法就是將4 2 1 0 0變成0 1 2 3 3這種,儘量讓越前面的數越小,相同的數字替換後依舊相同),狀態數在k=5的時候也只有52。然後暴力建出矩陣再快速冪即可。
關於這題的具體實現以及轉移矩陣的構造,我個人YY出一種比較方便的寫法:首先對所有小於等於k位的(不足補0)k+1進位制下的數s,都算出它替換成最小表示法後的結果Min[s]。如果Min[s]=s,這就是合法狀態,於是在矩陣中為它開出一位並記錄下來。容易發現合法狀態中一定不會出現某一位的值為k。然後掃一遍所有合法狀態,再用列舉第i+1個點是否向i~i-k+1連邊。如果連了兩個已經屬於相同連通塊的點則不合法;如果沒有點和第i-k+1個點屬於同一個連通塊,而i+1又沒有向它連邊則不合法。確定連邊情況合法後,將i+1涉及到的所有連通塊的點的值全部變為k,然後呼叫Min陣列即可得到其最小表示。
最後說一下初始狀態的權值。例如初始狀態為0 1 2 1 2,則它的權值為,其中f[i]表示i個點的完全圖生成樹個數。由於本題只需要算到f[5],可以手玩可以暴力。不過根據我高一時Semiwaker講樹的prufer序列之類的知識,。
CODE:
#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn=60;
const int maxs=20000;
const long long M=65521;
typedef long long LL;
struct mat
{
LL val[maxn][maxn];
} e,a;
int num[maxn];
LL pre[maxn];
int N=0;
int Min[maxs];
int id[maxs];
int temp[10];
int cnt[10];
LL f[10];
int ms,k;
LL n;
mat Times(mat x,mat y)
{
mat z;
for (int i=0; i<N; i++)
for (int j=0; j<N; j++) z.val[i][j]=0;
for (int i=0; i<N; i++)
for (int j=0; j<N; j++)
for (int w=0; w<N; w++)
z.val[i][j]=(z.val[i][j]+ x.val[i][w]*y.val[w][j] )%M;
return z;
}
int main()
{
freopen("count.in","r",stdin);
freopen("count.out","w",stdout);
scanf("%d%I64d",&k,&n);
if (k==1)
{
printf("1\n");
return 0;
}
ms=1;
for (int i=1; i<=k; i++) ms*=(k+1);
for (int i=0; i<ms; i++)
{
int s=i;
for (int j=1; j<=k; j++) temp[j]=s%(k+1),s/=(k+1);
int x=-1;
for (int j=0; j<=k; j++) cnt[j]=-1;
for (int j=1; j<=k; j++)
if (cnt[ temp[j] ]!=-1) temp[j]=cnt[ temp[j] ];
else cnt[ temp[j] ]=++x,temp[j]=x;
s=0;
for (int j=k; j>=1; j--) s=s*(k+1)+temp[j];
Min[i]=s;
}
for (int i=0; i<ms; i++) id[i]=-1;
for (int i=0; i<ms; i++)
if (Min[i]==i)
{
id[i]=N;
num[N]=i;
N++;
}
int mt=(1<<k);
for (int i=0; i<N; i++)
for (int t=0; t<mt; t++)
{
int s=num[i];
for (int j=1; j<=k; j++) temp[j]=s%(k+1),s/=(k+1);
bool sol=true;
for (int j=1; j<k; j++) if (t&(1<<(j-1)))
for (int w=j+1; w<=k; w++) if (t&(1<<(w-1)))
if (temp[j]==temp[w]) sol=false;
if (!sol) continue;
bool zero=false;
for (int j=2; j<=k; j++) if (!temp[j]) zero=true;
if ( !zero && !(t&1) ) continue;
for (int j=0; j<=k; j++) cnt[j]=0;
for (int j=1; j<=k; j++) if (t&(1<<(j-1))) cnt[ temp[j] ]=-1;
for (int j=1; j<=k; j++) if (cnt[ temp[j] ]==-1) temp[j]=k;
s=0;
for (int j=k; j>=1; j--) s=s*(k+1)+temp[j];
s/=(k+1);
s+=(k*ms/(k+1));
s=id[ Min[s] ];
a.val[i][s]++;
}
f[0]=1;
f[1]=1;
f[2]=1;
f[3]=3;
f[4]=16;
f[5]=125; //f[n]=n^(n-2)
for (int i=0; i<N; i++)
{
int s=num[i];
for (int j=1; j<=k; j++) temp[j]=s%(k+1),s/=(k+1);
for (int j=0; j<=k; j++) cnt[j]=0;
for (int j=1; j<=k; j++) cnt[ temp[j] ]++;
pre[i]=1;
for (int j=0; j<=k; j++) pre[i]*=f[ cnt[j] ];
}
for (int i=0; i<N; i++) e.val[i][i]=1;
n-=k;
mat b=e;
for (int i=61; i>=0; i--)
{
b=Times(b,b);
if (n&(1LL<<i)) b=Times(b,a);
}
LL ans=0;
for (int i=0; i<N; i++) ans=(ans+ pre[i]*b.val[i][0] )%M;
printf("%I64d\n",ans);
return 0;
}