題意:
給定n個碎片的轉移關係,問最多使用m個碎片組成的不同的序列個數是多少。
思路:
容易想到dp的方法,以
dp[i][j]
表示長度爲i以j號碎片結尾的不同序列的數量。那麼dp[i + 1][k] += dp[i][j](j -> k 可以轉移)dp[0][0] = 1;
最後的答案即爲整個dp[i][j]數組所有元素的和。但是一算複雜度,超時。
於是乎想到矩陣,關係矩陣A的k次方可以求出任意長度爲k+1的序列總的數量。但是這道題要求最多長度爲m的總數量,也就是說要求A^0 + A^1 + A^2… + A^m - 1。如果分別用快速冪,算一算複雜度依舊超時。然後就發現了構造矩陣的神奇方法。構造的矩陣B如下:
A . . . A 1
A . . . A 1
. .. . …. A 1
. . . . A 1
A . . . A 1
0 . . . 0 1
也就是在原來的轉移矩陣最右邊加上一列1,用於保存上一步以及之前的總的答案,
最後的答案爲矩陣B^(m-1) 的所有項的和。時間複雜度O(n^3 *log(m)),注意特判m = 1(論構造矩陣的神奇)
附搓代碼:
#include <cstdio>
#include <cstring>
using namespace std;
const int piece = 55;
int ch[piece][piece];
int ans[piece][piece];
int tp[piece][piece];
int n, m;
void cal(int a[][piece], int b[][piece], int c[][piece])
{
for(int i = 1; i <= n + 1; i++)
for(int j = 1; j <= n + 1; j++)
{
int tmp = 0;
for(int k = 1; k <= n + 1; k++)
tmp = (tmp + a[i][k] * b[k][j]) % 2015;
tp[i][j] = tmp;
}
memcpy(c, tp, sizeof(tp));
}
void rapid(int a[][piece], int k, int b[][piece])
{
for(int i = 1; i <= n + 1; i++) b[i][i] = 1;
while(k)
{
if(k & 1)
cal(b, a, b);
k >>= 1;
cal(a, a, a);
}
}
int main()
{
// freopen("5411.in","r",stdin);
// freopen("5411out.txt","w",stdout);
int t;
scanf("%d", &t);
while(t--)
{
scanf("%d%d", &n, &m);
memset(ch, 0, sizeof(ch));
for(int i = 1; i <= n; i++)
{
int k;
scanf("%d", &k);
for(int j = 0; j < k; j++)
{
int tmp;
scanf("%d", &tmp);
ch[i][tmp] = 1;
}
}
if(m == 1)
printf("%d\n", n + 1);
else
{
memset(ans, 0, sizeof(ans));
for(int i = 1; i <= n + 1; i ++)
ch[i][n + 1] = 1;
for(int i = 1 ; i <= n; i++)
ch[n + 1][i] = 0;
rapid(ch, m - 1, ans);
int ls = 0;
for(int i = 1; i <= n + 1; i++)
for(int j = 1; j <= n + 1; j++)
ls = (ls + ans[i][j]) % 2015;
printf("%d\n", ls);
}
}
}