count (類插頭DP 矩陣快速冪)

2021-08-17 10:00:42 字數 3321 閱讀 1316

題目大意:有n個點,編號為1~n。第i個點和第j個點之間有一條無向邊當且僅當|i-j|<=k。求這個圖的生成樹個數。k≤

5,n≤

1015

k ≤5

,n≤10

15。

題目分析: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。然後掃一遍所有合法狀態,再用2k

2

k列舉第i+1個點是否向i~i-k+1連邊。如果連了兩個已經屬於相同連通塊的點則不合法;如果沒有點和第i-k+1個點屬於同乙個連通塊,而i+1又沒有向它連邊則不合法。確定連邊情況合法後,將i+1涉及到的所有連通塊的點的值全部變為k,然後呼叫min陣列即可得到其最小表示。

最後說一下初始狀態的權值。例如初始狀態為0 1 2 1 2,則它的權值為f[

1]∗f

[2]∗

f[2]

f [1

]∗f[

2]∗f

[2

],其中f[i]表示i個點的完全圖生成樹個數。由於本題只需要算到f[5],可以手玩可以暴力。不過根據我高一時semiwaker講樹的prufer序列之類的知識,f[

n]=n

n−2 f[n

]=nn

−2

。code:

#include

#include

#include

#include

#include

#include

#include

#include

using

namespace

std;

const

int maxn=60;

const

int maxs=20000;

const

long

long m=65521;

typedef

long

long ll;

struct mat

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)

int main()

ms=1;

for (int i=1; i<=k; i++) ms*=(k+1);

for (int i=0; iint 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; i1;

for (int i=0; iif (min[i]==i)

int mt=(1

for (int j=1; j<=k; j++) temp[j]=s%(k+1),s/=(k+1);

bool sol=true;

for (int j=1; jif (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; iint 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; i1;

n-=k;

mat b=e;

for (int i=61; i>=0; i--)

Count(矩陣快速冪)

學習矩陣快速冪可以看這篇部落格 矩陣快速冪高階 description farmer john有n頭奶牛.某天奶牛想要數一數有多少頭奶牛,以一種特殊的方式 第一頭奶牛為1號,第二頭奶牛為2號,第三頭奶牛之後,假如當前奶牛是第n頭,那麼他的編號就是2倍的第n 2頭奶牛的編號加上第n 1頭奶牛的編號再加...

Count(矩陣快速冪)

farmer john有n頭奶牛.某天奶牛想要數一數有多少頭奶牛,以一種特殊的方式 第一頭奶牛為1號,第二頭奶牛為2號,第三頭奶牛之後,假如當前奶牛是第n頭,那麼他的編號就是2倍的第n 2頭奶牛的編號加上第n 1頭奶牛的編號再加上自己當前的n的三次方為自己的編號.現在farmer john想知道,第...

hdu6470 Count 矩陣快速冪

奶牛的編號規則 當前奶牛是第n頭,那麼他的編號就是2倍的第n 2頭奶牛的編號加上第n 1頭奶牛的編號再加上自己當前的n的三次方為自己的編號.第一頭奶牛為1號,第二頭奶牛為2號 第n頭奶牛的編號是多少,答案模123456789 題解 給你公式 很明顯這是一道矩陣快速冪的題目 f n f n 1 f n...