zoukankan      html  css  js  c++  java
  • 刷题记录[SDOI2009]HH去散步(动态规划、矩阵快速乘、点边互换思想)

    链接https://www.luogu.org/problemnew/show/P2151

    题目描述
    HH有个一成不变的习惯,喜欢饭后百步走。所谓百步走,就是散步,就是在一定的时间 内,走过一定的距离。 但是同时HH又是个喜欢变化的人,所以他不会立刻沿着刚刚走来的路走回。 又因为HH是个喜欢变化的人,所以他每天走过的路径都不完全一样,他想知道他究竟有多 少种散步的方法。
    现在给你学校的地图(假设每条路的长度都是一样的都是1),问长度为t,从给定地 点A走到给定地点B共有多少条符合条件的路径
    输入输出格式
    输入格式:

    第一行:五个整数N,M,t,A,B。其中N表示学校里的路口的个数,M表示学校里的 路的条数,t表示HH想要散步的距离,A表示散步的出发点,而B则表示散步的终点。
    接下来M行,每行一组Ai,Bi,表示从路口Ai到路口Bi有一条路。数据保证Ai != Bi,但 不保证任意两个路口之间至多只有一条路相连接。 路口编号从0到N − 1。 同一行内所有数据均由一个空格隔开,行首行尾没有多余空格。没有多余空行。 答案模45989。
    输出格式:

    一行,表示答案。
    说明
    对于30%的数据,N ≤ 4,M ≤ 10,t ≤ 10。
    对于100%的数据,N ≤ 50,M ≤ 60,t ≤ 2^30,0 ≤ A,B

    关于这道题,首先最容易想到一个线性代数知识点:
    若使用一个矩阵表示图上两点的有一条连边,则这个矩阵的N次方中的一个元素aija_{ij}表示从点i经过N步到达点j的方案数
    可以很容易通过数学归纳法证明
    这个方法的优势在与由于矩阵乘法具有结合律,可以使用二分法优化
    但是这题有一个特殊的条件:不会立刻沿着刚刚回来的路走回
    这导致传统的方法失效了
    为了解决这个问题,我们采用点边互换的思想,即将无向边变成两个方向相反的有向边,并对这些有向边进行编号,并当作点处理
    显然,两点之间联通的条件为两个点在原图中所对应的两条有向边满足一个边的出发点是另一个边的终点,并且两条边不是来源于同一条无向边。
    如何判断是否来自同一条无向边呢?
    拆分无向边为两条有向边时我们采用相邻编号,且编号从2开始。则当任意一个编号异或1等于另一个编号时则这两个编号来自同一个无向边。
    这样处理之后,我们得到了一个新的矩阵。
    在原图中,由于经过m个节点的路径长度为m+1,因此在新的矩阵中我们只需求新矩阵的t-1次幂就能得到答案。接下来有两种做法:
    第一种:建立一个新矩阵,设立两个虚点(相当于原图中的虚边)使两个虚点分别与所有从出发点出发的边、所有到终点结束的边联通,用这个矩阵与刚才的矩阵相乘,统计两个虚点之间的方案数输出
    第二种:直接统计所有从出发点出发的边、所有到终点结束的边之间的方案数求和输出
    我采用了后一种方案
    时间复杂度为O(m3lognm^3logn)

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    using namespace std;
    #define mod 45989
    #define ll long long
    int n,m,t,a,b;
    #define N 200
    #define next nico
    int head[N],to[N],next[N],from[N],tot=1;
    void add(int a,int b)
    {
        to[++tot]=b;
        from[tot]=a;
        next[tot]=head[a];
        head[a]=tot;
    }
    struct mat
    {
        int a[130][130];
        mat operator *(const mat b)const
        {
            mat ans;
            for(int i = 1 ; i <= tot ; i ++)
            for(int j = 1 ; j <= tot ; j ++)
            ans.a[i][j]=0;
            for(int i = 1 ; i <= tot ; i ++)
            for(int j = 1 ; j <= tot ; j ++)
            {
            for(int k = 1 ; k <= tot ; k ++)
            {
                ans.a[i][j]=(((ll)a[i][k]*b.a[k][j])%mod+ans.a[i][j])%mod;
            }
            }
            return ans;
        }
    }buf,answer;
    mat pow(mat a,int n)
    {
        if(n==1)return a;
        mat ans = pow(a,n/2);
        ans=ans*ans;
        if(n%2==1)
        ans=ans*a;
        return ans;
    }
    void build()
    {
        for(int i = 2; i <= tot; i ++)
        for(int j = 2 ;j <= tot; j ++)
        if(j!=(i^1)&&from[i]==to[j])
        {
            buf.a[i][j]=1;
        }
    }
    int main()
    {
        scanf("%d%d%d%d%d",&n,&m,&t,&a,&b);
        for(int i = 1; i <= m ; i ++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            add(x+1,y+1);add(y+1,x+1);
        }
        build();
        a++;b++;
        answer=pow(buf,t-1);
        int ans = 0;
        for(int i = 2; i <= tot ; i ++ )
        for(int j = 2; j <= tot ; j ++ )
        if(from[i]==a&&to[j]==b)
        ans=(ans+answer.a[j][i])%mod;
        printf("%d",ans);
    }
  • 相关阅读:
    C#线程同步lock,Monitor,Mutex,同步事件和等待句柄(1)
    多线程 C#解决方案小结
    10款交互设计原型开发工具
    创建自定义配置节
    禁用外键 oracle
    GUIDESIGNSTUDIO3中文帮助(1)欢迎使用 GUI Design Studio 3.0
    大数据量的数据库设计思考
    gui design studio 3 中文帮助(2)获取帮助
    NHibernate之旅(24):探索NHibernate二级缓存(下)(转)
    客户端原型GUI Design Studio
  • 原文地址:https://www.cnblogs.com/akonoh/p/10216766.html
Copyright © 2011-2022 走看看