矩阵乘法

为君一笑 提交于 2021-02-11 03:13:24

  关于矩阵乘法的定义我 就不再多说 关键是考验的还是建模能力 和 问题的转换问题。

我自认没有学长做的好但是我想 也具有一般的建模能力了(莫名自信)。

入坑矩阵是这样的故事

%15000 这种大法我不屑使用 所以入坑矩阵乘法当时学的很基础只知道其 能加速递推(利用快速幂)

现在的话是大概学会了建模 就是构造矩阵进行 乘法 当然 一般矩阵乘法都是要进行矩阵快速幂的。。

放上我年轻时青涩的代码:

#include<bits/stdc++.h>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<iostream>
#include<iomanip>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<algorithm>
#define mod 10000
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int a[6][6],b[6][6],c[6][6];
int k;
void fastpow(int p)
{
    b[1][1]=a[1][1];b[1][2]=a[1][2];
    b[2][1]=a[2][1];b[2][2]=a[2][2];
    while(p!=0)
    {
        if((p&1)==1)
        {
            c[1][1]=a[1][1];c[1][2]=a[1][2];
            c[2][1]=a[2][1];c[2][2]=a[2][2];
            for(int i=1;i<=2;i++)
            {
                for(int j=1;j<=2;j++)
                {
                    a[i][j]=0;
                    for(int k=1;k<=2;k++)
                    {
                        a[i][j]+=(c[i][k]*b[k][j])%mod;
                    }
                }
            }
        }
        c[1][1]=b[1][1];c[1][2]=b[1][2];
        c[2][1]=b[2][1];c[2][2]=b[2][2];
        for(int i=1;i<=2;i++)
        {
            for(int j=1;j<=2;j++)
            {
                b[i][j]=0;
                for(int k=1;k<=2;k++)
                {
                    b[i][j]+=(c[i][k]*c[k][j])%mod;
                }
            }
        }
        p>>=1;
    }
}
int main()
{
    //freopen("1.in","r",stdin);
    a[1][1]=1;a[1][2]=1;
    a[2][1]=1;a[2][2]=0;
    while(1)
    {
        k=read();if(k==0){printf("0\n");continue;}
        if(k==-1)break;
        fastpow(k-1);
        printf("%d\n",a[2][1]%mod);
        a[1][1]=1;a[1][2]=1;
        a[2][1]=1;a[2][2]=0;
    }
    return 0;
}
View Code

当时一些细节都不是很会 现在就比较炉火纯青了。

这个暴力是可以拿到 60分的 (实测)dp 一下即可 关键考察 发现这其实是个递推且有递推时

构造一个和上一道题差不多的矩阵进行快速幂即可 。

//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<cstring>
#include<string>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<utility>
#include<set>
#include<bitset>
#include<queue>
#include<stack>
#include<deque>
#include<map>
#include<vector>
#include<ctime>
#define ll long long
#define INF 2147483646
#define mod 7777777
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline ll read()
{
    ll x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
inline void put(ll x)
{
    x<0?putchar('-'),x=-x:0;
    ll num=0;char ch[50];
    while(x)ch[++num]=x%10+'0',x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(ch[num--]);
    putchar('\n');return;
}

const ll MAXN=15;
ll n,k;
ll f[MAXN],tmp[MAXN];
ll a[MAXN][MAXN],b[MAXN][MAXN];

void curcluate()
{
    for(ll j=1;j<=k;++j)a[j+1][j]=1;
    for(ll i=1;i<=k;++i)a[i][k]=1;
    /*for(ll i=1;i<=k;++i)
    {    
        for(ll j=1;j<=k;++j)
            cout<<a[i][j]<<' ';
        cout<<endl;
    }*/
}
void mul()
{
    memset(tmp,0,sizeof(tmp));
    for(ll i=1;i<=k;++i)
        for(ll j=1;j<=k;++j)
            tmp[i]=(tmp[i]%mod+(f[j]*a[j][i])%mod)%mod;
    for(ll i=1;i<=k;++i)f[i]=tmp[i]%mod;
}

void mul1()
{
    memset(b,0,sizeof(b));
    for(ll i=1;i<=k;++i)
        for(ll j=1;j<=k;++j)
            for(ll w=1;w<=k;++w)
                b[i][j]=(b[i][j]%mod+(a[i][w]%mod*a[w][j]%mod)%mod);
    for(ll i=1;i<=k;++i)
        for(ll j=1;j<=k;++j)
            a[i][j]=b[i][j]%mod;
}

void fastpow(ll p)
{
    while(p)
    {
        if(p&1)mul();
        p=p>>1;
        mul1();
    }
    return;
}

int main()
{
    //freopen("1.in","r",stdin);
    k=read();n=read();f[0]=1;
    for(ll i=1;i<=k;++i)
        for(ll j=1;j<=k;++j)
            if(i>=j)f[i]+=f[i-j];
    //for(ll i=1;i<=k;++i)cout<<f[i]<<endl;
    if(n<=k)put(f[n]);
    else
    {
        curcluate();
        fastpow(n-k);
        put(f[k]);
    }
    return 0;
}
View Code

这道题就比较有意思了 秒构造矩阵 能推出 第n项 但是答案要求从第m项加到第n项 此时 茫然了 全部推出来累加铁定T

这是再次建模 两点之间的数的累加 这不前缀和一下就好了么。考虑在原矩阵上再加一个数字 表示从第1项到当前第k项的和

这时状态矩阵相应改变 此时我们只需要求出 m-1 和 n即可。

//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<cstring>
#include<string>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<utility>
#include<set>
#include<bitset>
#include<queue>
#include<stack>
#include<deque>
#include<map>
#include<vector>
#include<ctime>
#define INF 2147483646
#define ll long long
#define R register
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline ll read()
{
    ll x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
inline void put(ll x)
{
    x<0?putchar('-'),x=-x:0;
    ll num=0;char ch[50];
    while(x)ch[++num]=x%10+'0',x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(ch[num--]);
    putchar('\n');return;
}
const ll MAXN=20;
ll b[MAXN],c[MAXN],tmp[MAXN];
ll n,m,mod,cnt,maxx,k,sum;
ll a[MAXN][MAXN],w[MAXN][MAXN],g[MAXN][MAXN];
ll f[MAXN],s[MAXN];
void mul()
{
    memset(w,0,sizeof(w));
    for(int i=1;i<=k;++i)
        for(int j=1;j<=k;++j)
            for(int u=1;u<=k;++u)
                w[i][j]=(w[i][j]+g[i][u]*g[u][j]%mod)%mod;
    for(int i=1;i<=k;++i)
        for(int j=1;j<=k;++j)
            g[i][j]=w[i][j];
    return;
}
void fastpow(ll p)
{
    for(int i=1;i<=k;++i)
    {
        f[i]=s[i];
        for(int j=1;j<=k;++j)g[i][j]=a[i][j];
    }
    while(p)
    {
        if(p&1)
        {
            memset(tmp,0,sizeof(tmp));
            for(int i=1;i<=k;++i)
                for(int j=1;j<=k;++j)
                    tmp[i]=(tmp[i]+f[j]*g[j][i]%mod)%mod;
            for(int i=1;i<=k;++i)f[i]=tmp[i];
        }
        p=p>>1;
        mul();
    }
    return;
}
int main()
{
    //freopen("1.in","r",stdin);
    k=read();
    for(int i=1;i<=k;++i)b[i]=read();
    for(int j=k;j>=1;--j)c[j]=read();//注意倒序
    m=read();n=read();mod=read();
    if(n<=k)
    {
        for(int i=m;i<=n;++i)cnt+=b[i]%mod;
        put(cnt%mod);
        return 0;
    }
    cnt=0;
    for(int i=1;i<=k;++i)s[i]=b[i]%mod,cnt=(cnt+b[i]%mod)%mod;
    s[k+1]=cnt;
    for(int i=1;i<=k;++i)a[i+1][i]=1;
    a[k+1][k]=0;
    for(int i=1;i<=k;++i)a[i][k]=c[i]%mod;
    ++k;
    for(int i=1;i<=k;++i)a[i][k]=c[i]%mod;
    a[k][k]=1;
    /*for(int i=1;i<=k;++i)cout<<s[i]<<endl;
    for(int i=1;i<=k;++i)
    {
        for(int j=1;j<=k;++j)
            cout<<a[i][j]<<' ';
        puts("");
    }*/
    fastpow(n-k+1);
    maxx=f[k]%mod;
    if(m-1<k)
    {
        for(int i=1;i<=m-1;++i)sum=(sum%mod+s[i])%mod;
        put((maxx-sum+mod)%mod);
        return 0;
    }
    fastpow(m-k);
    put((maxx-(f[k]%mod)+mod)%mod);
    return 0;
}
View Code

写的是有一些繁琐了。

这道题我认为就有点意思了 点边转换想不到这么巧妙的转换 原因只在题目中要求的他不会从刚刚的原路返回。

如果可以原路返回的话直接无脑搞即可。像我如下无脑代码:

//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<cstring>
#include<string>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<utility>
#include<set>
#include<bitset>
#include<queue>
#include<stack>
#include<deque>
#include<map>
#include<vector>
#include<ctime>
#define INF 2147483646
#define ll long long
#define R register
#define mod 45989
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline int read()
{
    int x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
inline void put(int x)
{
    x<0?putchar('-'),x=-x:0;
    int num=0;char ch[50];
    while(x)ch[++num]=x%10+'0',x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(ch[num--]);
    putchar('\n');return;
}
const int MAXN=52;
int n,m,t;
int a[MAXN][MAXN];//广义矩阵乘法
int tmp[MAXN][MAXN],b[MAXN][MAXN];
int st,en;
void mul()
{
    memset(tmp,0,sizeof(tmp));
    for(int i=0;i<n;++i)
        for(int j=0;j<n;++j)
            for(int k=0;k<n;++k)
                tmp[i][j]=(tmp[i][j]%mod+a[i][k]*a[k][j]%mod)%mod;
    for(int i=0;i<n;++i)
        for(int j=0;j<n;++j)
            a[i][j]=tmp[i][j];
    return;
}
void fastpow(int p)
{
    while(p)
    {
        if(p&1)
        {
            memset(tmp,0,sizeof(tmp));
            for(int i=0;i<n;++i)
                for(int j=0;j<n;++j)
                    for(int k=0;k<n;++k)
                        tmp[i][j]=(tmp[i][j]%mod+b[i][k]*a[k][j]%mod)%mod;
            for(int i=0;i<n;++i)
                for(int j=0;j<n;++j)
                    b[i][j]=tmp[i][j];
        }
        p=p>>1;
        mul();
    }
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();t=read();st=read();en=read();
    for(int i=1;i<=m;++i)
    {
        int x,y;
        x=read();y=read();
        a[x][y]=a[y][x]=1;
        b[x][y]=b[y][x]=1;
    }
    if(t<=1){put(a[st][en]);return 0;}
    fastpow(t-1);
    put(b[st][en]%mod);
    return 0;
}
View Code

但是 为了保证这个需求 我们不能从刚刚的原路范围维护一些信息的话接下来的信息又会被覆盖所以这种直接点与点直接连边一定会出现错误。

这时考虑边和边直接连边 把矩阵当中的点变成边的编号 对于一条无向边变成两条边然后给边附上编号转换成两个点且不连边。

此时对于同向边的点 可以进行连边然后做一发矩阵乘法就应该是满足题意的了。

被学长的代码带歪了  有些细节的地方他似乎写错了 hzw救了我诶。

首先我先虚化一个边 这个边定义为到达起点的边 那么这条边理应和从 st的各种出边相连。

然后便利每一条边 让他们相连,然后建图完成 每条边个点都是单向的且保证了 不会从原路返回。

对着黄学长的代码 在写 没想到细节较多 写的我非常恶心 我理解其中含义可能是结构体的重载运算符让我吃不消吧,

首先 总结一下这道题的完整的全部思路 :发现不能走重边 必须得点边转换 

起点的话新开一条边表示到达起点的边 以这条边和起点发出的边相连 然后 这就是初始的答案矩阵了表示 从起点到终点经过一条边的答案。

当然询问答案的话必须询问终点的入边的编号 因为这样代表着从跑到终点的点 总之还是有一点迷但是。

核心思想是 为了不走重边所以 可以这样做不走重边且方案数正确 正确性不言而喻。

宏观的想就比较容易理解了。

从T 到 S 经过 N条边 的最短路。

这个 是广义的矩阵乘法 关键是考验建模的能力,每进行一次矩乘 都会增加一条边 考虑floyd 的思想 从 i 到 j的最短路 由 i-k 到 k-j来更新。

每次在转移的时候取min即可 。很值得思考的题目!

//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<cstring>
#include<string>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<utility>
#include<set>
#include<bitset>
#include<queue>
#include<stack>
#include<deque>
#include<map>
#include<vector>
#include<ctime>
#define ll long long
#define INF 2147483646
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc()
{
    return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
inline ll read()
{
    ll x=0,f=1;char ch=getc();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    return x*f;
}
inline void put(ll x)
{
    x<0?putchar('-'),x=-x:0;
    ll num=0;char ch[50];
    while(x)ch[++num]=x%10+'0',x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(ch[num--]);
    putchar(' ');return;
}
const ll MAXN=202;
struct wy
{
    ll x,y,z;
}t[MAXN];
ll a[MAXN][MAXN];
ll c[MAXN][MAXN],b[MAXN][MAXN];
inline ll min(ll x,ll y){return x>y?y:x;}
ll n,m,s,e;
ll tmp[MAXN],cnt,num,flag;
void discrete()
{
    sort(tmp+1,tmp+cnt+1);
    for(ll i=1;i<=cnt;++i)if(tmp[i]!=tmp[i-1])tmp[++num]=tmp[i];
    for(ll i=1;i<=m;++i)
    {
        x(i)=lower_bound(tmp+1,tmp+1+num,x(i))-tmp;
        y(i)=lower_bound(tmp+1,tmp+1+num,y(i))-tmp;
        a[x(i)][y(i)]=a[y(i)][x(i)]=z(i);
    }
}
void fastpower(ll p)
{
    while(p)
    {
        if(p&1)//a+c
        {
            if(flag==0)
            {
                for(ll i=1;i<=num;++i)
                    for(ll j=1;j<=num;++j)
                    {
                        c[i][j]=a[i][j];
                    }
                flag=1;
            }
            else
            {
                for(ll i=1;i<=num;++i)
                    for(ll j=1;j<=num;++j)
                        b[i][j]=c[i][j];
                for(ll i=1;i<=num;++i)//枚举列
                {
                    for(ll j=1;j<=num;++j)//枚举行
                    {
                        ll minn=INF;
                        for(ll k=1;k<=num;++k)
                        {
                            minn=min(minn,a[i][k]+b[k][j]);
                        }
                        c[i][j]=minn;
                    }
                }
            }
        }
        p=p>>1;
        for(ll i=1;i<=num;++i)
            for(ll j=1;j<=num;++j)
                b[i][j]=a[i][j];
        for(ll i=1;i<=num;++i)//枚举列
                {
                    for(ll j=1;j<=num;++j)//枚举行
                    {
                        ll minn=INF;
                        for(ll k=1;k<=num;++k)
                        {
                            minn=min(minn,b[i][k]+b[k][j]);
                        }
                        a[i][j]=minn;
                    }
                }
    }
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();m=read();
    s=read();e=read();
    memset(a,5,sizeof(a));
    for(ll i=1;i<=m;++i)
    {
        z(i)=read();
        x(i)=read();
        y(i)=read();
        tmp[++cnt]=x(i);
        tmp[++cnt]=y(i);
    }
    discrete();
    s=lower_bound(tmp+1,tmp+1+num,s)-tmp;
    e=lower_bound(tmp+1,tmp+1+num,e)-tmp;
    /*for(ll i=1;i<=num;++i)
    {    
        for(ll j=1;j<=num;++j)
        {
            cout<<a[i][j]<<' ';
        }
        cout<<endl;
    }*/
    fastpower(n);
    put(c[s][e]);
    return 0;
}
View Code

有时间要复习。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!