【题解】CF1662C European Trip
\(n \le 100\) 个点的无向图,求有多少长度为 \(k\le 10^4\) 的从 \(x\) 出发的回路满足不存在一条边来回走的情况,即路径序列不存在 \(a\to b\to a\)。对每个 \(x\in[1,n]\) 求和。
实际上问题等价于对每个 \(x\) 求答案。
如果我们直接按题意模拟 DP,记录最后走的一条边,或者路径的倒数两个点可以轻易的做到 \(n^4k\)。
事实上以上方法基本没有优化的余地,因为状态就已经非常大了。
考虑直接记录状态 \(f_{k,i,j}\) 表示从 \(i\) 到 \(j\) 的长度为 \(k\) 的合法路径方案,然后用容斥统计答案。
显然 \(k=1\) 时就是邻接矩阵,\(k=2\) 时是邻接矩阵的平方减去度数矩阵。
对于 \(k>2\) 的情况,\(f_{k} = f_{k - 1} \times A - f_{k - 2}\times (D-I)\)。
\(A\) 表示邻接矩阵,\(D\) 表示度数矩阵。这里减去单位矩阵 \(I\) 是因为倒数第二、三条边相等的情况不会被 \(f_{k - 1}\times A\) 算进去。
这样做可以做到 \(\mathcal{O}(n^3k)\),还是过不去。但是我们发现递推的形式是类似斐波那契的常系数齐次线性递推,只不过由整数变成了矩阵。幸运的是,矩阵套矩阵仍然可以矩阵快速幂,因为两个满足结合律的叠加仍然满足结合律,所以我们直接矩阵加速递推即可,只不过转移矩阵的元素仍是矩阵。时间复杂度 \(\mathcal{O}(n^3\log k)\)。
How Linear Algebra's mind work?
#define N 105
int n, m, k;
struct node{
int a[N][N];
}A, D, I, Z, S1, S2;
node operator*(node x, node y){
node z; memset(z.a, 0, sizeof(z.a));
rp(i, n)rp(j, n)rp(k, n)z.a[i][j] = (z.a[i][j] + x.a[i][k] * 1LL * y.a[k][j]) % P;
return z;
}
node operator-(node x,node y){rp(i, n)rp(j, n)su(x.a[i][j], y.a[i][j]);return x;}
node operator+(node x,node y){rp(i, n)rp(j, n)ad(x.a[i][j], y.a[i][j]);return x;}
struct mat{node a[2][2];};
mat operator*(mat x, mat y){
mat z;
z.a[0][0] = (x.a[0][0] * y.a[0][0]) + (x.a[0][1] * y.a[1][0]);
z.a[0][1] = (x.a[0][0] * y.a[0][1]) + (x.a[0][1] * y.a[1][1]);
z.a[1][0] = (x.a[1][0] * y.a[0][0]) + (x.a[1][1] * y.a[1][0]);
z.a[1][1] = (x.a[1][0] * y.a[0][1]) + (x.a[1][1] * y.a[1][1]);
return z;
}
mat operator^(mat x, int y){
mat now; now.a[0][0] = now.a[1][1] = I, now.a[0][1] = now.a[1][0] = Z;
for(; y; y >>= 1, x = x * x)if(y & 1)now = now * x;
return now;
}
int main() {
read(n, m, k);
if(k <= 2){puts("0"); return 0;}
rp(i, n)I.a[i][i] = 1;
rp(i, m){
int x, y;
read(x, y);
A.a[x][y] = A.a[y][x] = 1;
D.a[x][x] ++, D.a[y][y] ++;
}
S1 = A, S2 = (A * A) - D;
k -= 2; mat w;
w.a[0][0] = A, w.a[0][1] = I, w.a[1][0] = I - D, w.a[1][1] = Z;
w = w ^ k;
node cur = (S2 * w.a[0][0]) + (S1 * w.a[1][0]);
int ans = 0;
rp(i, n)ad(ans, cur.a[i][i]);
printf("%d\n", ans);
return 0;
}