日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【矩阵乘法】生成树计数(luogu 2109/NOI 2007)

發布時間:2023/12/3 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【矩阵乘法】生成树计数(luogu 2109/NOI 2007) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

生成樹計數

luogu 2109

題目大意

有n個排成一列的點,把距離不超過k的點之間連邊,問這個圖的生成樹個數

輸入樣例

3 5

輸出樣例

75

樣例說明

樣例對應的圖如下:

數據范圍

解題思路

因為n十分大,不能直接2^m暴力枚舉(m為總邊數)
k十分小,可以從k入手
考慮到對某個點有貢獻的只有前k個點
我們可以設狀態表示前k個點的連接情況(即那幾個點連在了一起)
狀態如果直接用k位數表示肯定不行
考慮除掉無用狀態
對于著k個數,如果前面有和它相連的點,那么這個點的數值等于和它相連的點的數值,如果沒有,則它的數值等于前k個數中最大數值+1
例如:
k=5,1和3相連,2和5相連
則表示出來的狀態為12132
s1=++ws_1=++ws1?=++w
s2=++ws_2=++ws2?=++w
s3=s1s_3=s_1s3?=s1?
s4=++ws_4=++ws4?=++w
s5=s2s_5=s_2s5?=s2?
得出狀態后,我們輸出總裝臺數,k=5時竟只有52種!
用一個數組給各個狀態存一下新的編號(52種中的第幾種)
然后考慮狀態之間的轉移
對于所有用的狀態,2k2^k2k枚舉第k+1k+1k+1個點和kkk個點是否連邊
使其滿足以下條件
1.不連成環(即數值相同的點最多連一個,不然會形成環)
2.保證第1個點要和2~k+12\sim k+12k+1個點中的一個相連(要么和k+1k+1k+1相連,要么和2~k2\sim k2k個點中某個點的數值相等)
得出狀態后通過前面的計算方法,把新狀態的編號求出來,然后在矩陣中連起來
得到轉移矩陣后,就可以用矩陣乘法快速得出n個點的結果了

代碼

#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define ll long long #define wyc 65521 using namespace std; ll n, k, w, ww, s[10], ss[10], fa[10], p[100000]; struct matrix {ll n, m, a[100][100];matrix operator *(const matrix &b) const{matrix c;c.n = n;c.m = b.m;for (int i = 1; i <= c.n; ++i)for (int j = 1; j <= c.m; ++j)c.a[i][j] = 0;for (int i = 1; i <= c.n; ++i)for (int k = 1; k <= m; ++k)for (int j = 1; j <= c.m; ++j)c.a[i][j] = (c.a[i][j] + a[i][k] * b.a[k][j] % wyc) % wyc;return c;} }A, B; void dfs1(ll x, ll y, ll z) {if (x > k){p[z] = ++w;//新編號ll num = 1, g = 0;for (int i = 1; i <= k; ++i){g = 1;for (int j = 1; j <= s[i] - 2; ++j)//題目說明中n個點的完全圖生成樹個數為n^(n-2)g = g * s[i];num = num * g;//各種種數相乘}A.a[1][w] = num;//計入初始矩陣return;}for (int i = 1; i <= y; ++i){s[i]++;//這個數值的數+1dfs1(x + 1, y, z * 10 + i);//和前面已有的點相連s[i]--;}s[y + 1]++;//新的數值dfs1(x + 1, y + 1, z * 10 + y + 1);//不相連s[y + 1]--;return; } ll find(ll x) {return x == fa[x]?x:find(fa[x]);//并查集 } void pp(ll x, ll y) {if (x > k){ll num = 0, ww = 0;memset(ss, 0, sizeof(ss));for (int i = 2; i <= k + 1; ++i){ll g = find(i);if (!ss[g]) ss[g] = ++ww;//沒有相連的就新加一個數值num = num * 10 + ss[g];//用十進制存起來}if (ss[find(1)]) B.a[p[y]][p[num]]++;//第一個數有被加到,那就連接狀態return;}ll g = find(k + 1), gg = find(x);if (gg != g)//暫未相連{fa[g] = gg;//連接pp(x + 1, y);fa[g] = g;//沒有路徑壓縮所以可拆邊}pp(x + 1, y);//不練的情況return; } void dfs2(ll x, ll y, ll z) {if (x > k){memset(ss, 0, sizeof(ss));for (int i = 1; i <= k; ++i){if (!ss[s[i]]) ss[s[i]] = i;//找到相連的點中的第一個點,以這個點為父節點,ss和上面重復利用fa[i] = ss[s[i]];}fa[k + 1] = k + 1;pp(1, z);return;}for (int i = 1; i <= y; ++i)//再找一次有效狀態{s[x] = i;dfs2(x + 1, y, z * 10 + i);s[x] = 0;}s[x] = y + 1;dfs2(x + 1, y + 1, z * 10 + y + 1);s[x] = 0;return; } void Counting(ll x)//快速冪 {while(x){if (x&1) A = A * B;B = B * B;x>>=1;}return; } int main() {scanf("%lld%lld", &k, &n);dfs1(1, 0, 0);//得出編號dfs2(1, 0, 0);//連接狀態A.n = 1;A.m = B.n = B.m = w;Counting(n - k);//初始狀態已經是前k個printf("%lld", A.a[1][1]);return 0; }

總結

以上是生活随笔為你收集整理的【矩阵乘法】生成树计数(luogu 2109/NOI 2007)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。