[YZOJ]P1397-奔跑的骑士-矩阵建模

本文介绍了一种使用矩阵和快速幂方法解决特定图论问题的算法。该算法应用于寻找无向图中,从起点到终点的合法路径数量,且路径长度固定。通过对边的特殊表示和矩阵运算,有效地避免了非法回路的出现,并通过快速幂优化计算过程。

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

链接

题意描述:
给定一张无向图,一个起点一个终点。
定义一条合法路径为不存在任何一条满足A->B->A的关系的子路径。
求从起点到终点长为一个值的路径数。

数据范围:
对于30%的数据,N ≤ 4,M ≤ 10,t ≤ 10。
对于100%的数据,N ≤ 20,M ≤ 60,t ≤ 2147483647,0 ≤ A,B<=N-1。

解法:
30分做法:用队列维护走过当前长度到达的点,拓展转移可行转移。当长度达到定值的时候,若达到终点,则答案加一。
复杂度:最劣 O(nt)

100分做法:
由于转移次数较多,很自然的想到可以利用矩阵表示转移,用快速幂进行优化。

问题在于,如何用一个矩阵表示一系列转移?
用一个数列表示当前走到每个点的方案数。这些数在一步操作后会发生复杂的转移,累加到其他格子里面去,这正是矩阵所支持的。
若没有合法路径的限制?
最直观的想法是,在矩阵中,一行表示当前点被转移的来源。若存在j->i的路径,则 ai,j=1
这样乘上一个矩阵就表示进行了一次全部的转移。

如果加上路径的限制条件呢?
相当于只能“向前走”。可惜,点是不具有方向性的,所以我们把转移对象选择为边。边可以分成两条,分别为u->v和v->u,而按照这个想法,如果存在v->k,则边u->v可以向v->k转移。按照这个想法我们就重新建立了一个合法的矩阵。

#include<cstdio>
#include<algorithm>
#include<cstring>

#define R register
#define mod 45989
#define ll long long
using namespace std;
struct new_node{int a,b;}node[125];
int n,m,kk,A,B;
int nt;
int bs[125];

int read()
{
    R int xx;R char ch;
    while(ch=getchar(),ch<'0'||ch>'9');xx=ch-'0';
    while(ch=getchar(),ch>='0'&&ch<='9')xx=xx*10+ch-'0';
    return xx;
}

struct MAT
{
    int a[123][123];
    inline MAT operator * (const MAT &f)const
    {
        R MAT ret;
        R int i,j,k;
        for(i=0;i<=nt;++i)
            for(j=0;j<=nt;++j)
            {
                ret.a[i][j]=0;
                for(k=0;k<=nt;++k)
                    ret.a[i][j]=(ret.a[i][j]+a[i][k]*f.a[k][j])%mod;
            }
        return ret;
    }       
}v0,v1,ans;

void pow(int k)
{
    R MAT base=v1;
    ans=v0;
    while(k)
    {
        if(k&1)ans=ans*base;
        base=base*base;k>>=1;
    }
}

void build()
{
    R int i,j,x,y;
    memset(v0.a,0,sizeof(v0.a));
    memset(v1.a,0,sizeof(v1.a));
    memset(bs,0,sizeof(bs));
    nt=-1;
    for(i=1;i<=m;++i)
    {
        x=read(),y=read();
        node[++nt]=(new_node){x,y};
        node[++nt]=(new_node){y,x};
    }
    for(i=0;i<=nt;++i)
    {
        for(j=0;j<=nt;++j)
        {
            if(node[i].a==node[j].b&&i!=(j^1))
            v1.a[i][j]=1;
        }
    }
    for(i=0;i<=nt;++i)v0.a[i][i]=1;
}

void print()
{
    int sum=0;
    R int i,e;
    for(i=0;i<=nt;++i)
        if(node[i].a==A)
            bs[i]=1;
    for(e=0;e<=nt;++e)
        if(node[e].b==B)
        {
            for(i=0;i<=nt;++i)
                sum=(sum+(ll)bs[i]*ans.a[e][i])%mod;
        }
    printf("%d",sum);
}

int main()
{
    n=read(),m=read(),kk=read(),A=read(),B=read();
    build();pow(kk-1);print();
    return 0;
}
/*  
    void show()
    {
        printf("begin:\n");
        R int i,j;
        for(i=0;i<=nt;++i,printf("\n"))
            for(j=0;j<=nt;++j)
                printf("%d ",a[i][j]);
        printf("end\n");
    }   
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值