[ZJOI2005]沼泽鳄鱼

本文介绍了一种使用矩阵快速幂的方法来解决一个特定的问题:在一个无向图中,存在若干食人鱼进行周期性移动,求从起点到终点在特定时间内不遇到食人鱼的路径数量。通过构建邻接矩阵并利用矩阵乘法和快速幂运算,可以有效地解决该问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天继续攻集训队论文的我

题意

一张无向图,不超过 5050 个点
起点 ss 终点 t
一个单位时间移动一次
一些食人鱼作周期运动 长度不超过 44
人不能碰到食人鱼
时刻 u 到达终点 u<=2109u<=2∗109

分析

引理

我们观察数据发现nn十分的小,那么就可以直接用邻接矩阵
而且有一种矩阵叫矩阵乘法
对于一张(无向)图:G

G[i][j]=1i>jG[i][j]=1表示i−>j有一条边

那么我们想一想G2G2表示的意义是什么:
G[a][b]=k=0NG[a][k]G[k][b]G′[a][b]=∑k=0NG[a][k]∗G[k][b]

那么是不是就是表示2a>b走2布的时候a−>b的方案数,那么以此类推
kk布的方案数就是Gk了!

此题分析

我们用 AkAk 表示走kk布的情况下的方案数
Gi表示在走第 ii 布是的图的情况(0,1)表示
那么如果没有食人鱼的话就是:

Ak=Gk

Gu=Gu1GkGu=Gu−1∗Gk

那么如果有食人鱼的话就是
Ak=Ak1GkAk=Ak−1∗Gk

那么我们看见食人鱼的循环是2,3,42,3,4不等,但是lcm(2,3,4)=12lcm(2,3,4)=12
也就是意味着 G1=G13G1=G13 这两张图是一样的
那么我们可以O(n3)O(n3) 预处理出add矩阵
add=k=012Gkadd=∏k=012Gk

a=k/12,b=ka=k/12,b=k

那么答案的矩阵就是:
final=pow(add,a)1bGifinal=pow(add,a)∗∏1bGi记住矩阵乘法的左乘和右乘是不一样的

复杂度O(n3log2q)O(n3log2q)

代码:

#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#include <cstdio>
#include <cmath>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 55
#define M 25
#define R register
using namespace std;

int n, m, s, t, K, x, y, Nfish, Pow_time, leftn, ff[N], att[N][10];

struct matrix
{
    int a[N][N];
    matrix()
    {
        memset(a, 0, sizeof a);
    }
} G[20], fuu, ans, fff;

inline matrix operator * (matrix x, matrix y)
{
    matrix z;
    for(R int i = 0; i < n; ++i)
        for(R int j = 0; j < n; ++j)
            for(R int k = 0; k < n; ++k)
            {
                z.a[i][j] += x.a[i][k] * y.a[k][j];
                if(z.a[i][j] > 10000) z.a[i][j] %= 10000;
            }
    return z;
}


inline matrix Pow(matrix now, int y)
{
    matrix res;
    for(R int i = 0; i < n; ++i) res.a[i][i] = 1;
    while(y)
    {
        if(y & 1) res = res * now;
        now = now * now;
        y >>= 1;
    }
    return res;
}

inline void init()
{
    for(R int i = 1; i <= 12; i++)
    {
        G[i] = fuu;
        for(R int j = 1; j <= Nfish; j++)
        {
            int to = att[j][((i + 1) % ff[j]) ? ((i + 1) % ff[j]) : ff[j]];
            for(R int k = 0; k < n; k++)
                G[i].a[k][to] = 0;
        }
    }
}

inline matrix solve()
{
    Pow_time = K / 12;
    leftn = K % 12;
    matrix ans, fff;
    for(R int i = 0; i < n; ++i) ans.a[i][i] = fff.a[i][i] = 1;
    for(R int i = 1; i <= 12; ++i) fff = fff * G[i];
    ans = ans * Pow(fff, Pow_time);
    for(R int i = 1; i <= leftn; ++i)
    {
        ans = ans * G[i];
    }
    return ans;
}

inline int read()
{
    int x=0;
    char c=getchar();
    bool flag=0;
    while(c<'0'||c>'9'){if(c=='-')flag=1;   c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    return flag?-x:x;
}

int main()
{
    n = read(), m = read(), s = read(), t = read(), K = read();
    for(R int i = 1; i <= m; ++i)
    {
        x = read(), y = read();
        fuu.a[x][y] = fuu.a[y][x] = 1;
    }
    Nfish = read();
    for(R int i = 1; i <= Nfish; ++i)
    {
        ff[i] = read();
        for(int j = 1; j <= ff[i]; ++j)
            att[i][j] = read();
    }
    init();
    ans = solve();
    printf("%d\n", ans.a[s][t]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值