如果A是邻接矩阵,且i与j之间有A[i][j]条直接相连的道路。
那么A^k就是k长路矩阵,i与j之间有A^k[i][j]条k长的路。
题目要求小于等于M的道路的个数,对2015取模。
即∑∑∑A^k[i][j]%2015
1<=N<=50,一次矩阵乘法的运算量最高为O(125000),1<=M<=1e5,不可能将所有矩阵都算出来。
考虑计算出一个矩阵B[k],其中i与j之间有B[k][i][j]条小于等于k长的道路。
那么B[k+1]=B[k]*A+A
但是每次矩阵乘法之后都要加上一个矩阵,没有办法快速幂。
我们可以考虑构造一个全新的矩阵,矩阵上维护更多的量,以便快速幂。(以下下标均从0开始计算)
构造一个N+1阶的矩阵C,其中前N行和N列就是矩阵A,C[N][i]为i的入度,C[N][N]=1,其它为0。
初始矩阵为一个1行N+1列的矩阵D,除了D[N]=1外,其它都为0。
令E[k]=D*C^k
E[k][i]就表示以i为终点的小于等于k长的道路有多少种。
具体原理就是这个新的矩阵在一次乘法中完成了B[k+1]=B[k]*A+A的功能,通过维护更多的量。
总结:以后遇到矩阵快速幂的题目,可以考虑重构矩阵,用更多维来维护更多的量,以便完成复杂的转移。
代码
#include<stdio.h>
using namespace std;
const int maxn = 60;
const int mod = 2015;
struct Mat
{
int N;
int A[maxn][maxn];
void init(int n)
{
N=n;
for(int i=0;i<N;i++)
for(int j=0;j<N;j++)
A[i][j]=0;
}
void I()
{
for(int i=0;i<N;i++)
A[i][i]=1;
}
Mat operator * (const Mat& rhs) const
{
Mat ret;
ret.init(N);
for(int i=0;i<N;i++)
for(int j=0;j<N;j++)
for(int k=0;k<N;k++)
ret.A[i][j]=(ret.A[i][j]+A[i][k]*rhs.A[k][j])%mod;
return ret;
}
Mat operator ^ (int n)
{
Mat ret;
ret.init(N);
ret.I();
Mat x = *this;
while(n)
{
if(n&1) ret=ret*x;
x=x*x;
n>>=1;
}
return ret;
}
}MAT;
int N,M;
void read()
{
scanf("%d %d",&N,&M);
MAT.init(N+1);
int k,a;
for(int i=0;i<N;i++)
{
scanf("%d",&k);
for(int j=0;j<k;j++)
{
scanf("%d",&a);
MAT.A[i][a-1]++;
MAT.A[N][a-1]++;
}
}
MAT.A[N][N]=1;
}
void solve()
{
read();
MAT=MAT^(M-1);
int ans=0;
for(int i=0;i<N;i++)
ans=(ans+MAT.A[N][i])%mod;
printf("%d\n",(ans+N+1)%mod);
}
int main()
{
int T;
scanf("%d",&T);
while(T--) solve();
return 0;
}