1. 程式人生 > >刷題記錄[SDOI2009]HH去散步(動態規劃、矩陣快速乘、點邊互換思想)

刷題記錄[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次方中的一個元素 a i j

a_{ij} 表示從點i經過N步到達點j的方案數
可以很容易通過數學歸納法證明
這個方法的優勢在與由於矩陣乘法具有結合律,可以使用二分法優化
但是這題有一個特殊的條件:不會立刻沿著剛剛回來的路走回
這導致傳統的方法失效了
為了解決這個問題,我們採用點邊互換的思想,即將無向邊變成兩個方向相反的有向邊,並對這些有向邊進行編號,並當作點處理
顯然,兩點之間聯通的條件為兩個點在原圖中所對應的兩條有向邊滿足一個邊的出發點是另一個邊的終點,並且兩條邊不是來源於同一條無向邊。
如何判斷是否來自同一條無向邊呢?
拆分無向邊為兩條有向邊時我們採用相鄰編號,且編號從2開始。則當任意一個編號異或1等於另一個編號時則這兩個編號來自同一個無向邊。
這樣處理之後,我們得到了一個新的矩陣。
在原圖中,由於經過m個節點的路徑長度為m+1,因此在新的矩陣中我們只需求新矩陣的t-1次冪就能得到答案。接下來有兩種做法:
第一種:建立一個新矩陣,設立兩個虛點(相當於原圖中的虛邊)使兩個虛點分別與所有從出發點出發的邊、所有到終點結束的邊聯通,用這個矩陣與剛才的矩陣相乘,統計兩個虛點之間的方案數輸出
第二種:直接統計所有從出發點出發的邊、所有到終點結束的邊之間的方案數求和輸出
我採用了後一種方案
時間複雜度為O( m
3 l o g n m^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);
}