1. 程式人生 > >BZOJ4386[POI2015]Wycieczki——矩陣乘法+倍增

BZOJ4386[POI2015]Wycieczki——矩陣乘法+倍增

turn space col ont 就是 短路徑 註意 統計 aps

題目描述

給定一張n個點m條邊的帶權有向圖,每條邊的邊權只可能是1,2,3中的一種。
將所有可能的路徑按路徑長度排序,請輸出第k小的路徑的長度,註意路徑不一定是簡單路徑,即可以重復走同一個點。

輸入

第一行包含三個整數n,m,k(1<=n<=40,1<=m<=1000,1<=k<=10^18)。
接下來m行,每行三個整數u,v,c(1<=u,v<=n,u不等於v,1<=c<=3),表示從u出發有一條到v的單向邊,邊長為c。
可能有重邊。

輸出

包含一行一個正整數,即第k短的路徑的長度,如果不存在,輸出-1。

樣例輸入

6 6 11
1 2 1
2 3 2
3 4 2
4 5 1
5 3 1
4 6 3

樣例輸出

4

提示

長度為1的路徑有1->2,5->3,4->5。
長度為2的路徑有2->3,3->4,4->5->3。
長度為3的路徑有4->6,1->2->3,3->4->5,5->3->4。
長度為4的路徑有5->3->4->5。

因為邊權有三種,但邊數比較多,因此不能拆邊。但點數比較少可以把每個點拆成三個點,同一個點拆成的三個點要連上邊,這樣就能使邊權都是1了。

很容易想到用二分答案來求第k短路徑,但這是log2,顯然過不去,因此可以預處理出矩陣乘法的2i的矩陣,每次像倍增lca一樣如果能走這麽多步就走,不能走就嘗試2i-1的矩陣的答案數。

那麽怎麽統計答案?

可以建一個原點(0號點)連向所有拆點後的原圖節點,再將原點連向自己,這樣第一行每個數就是原點到達對應點步數小於等於矩陣冪次的總路徑數。

但這樣求的是2i-1步數的答案,因此還要記錄每個點的出度,統計時將每個答案乘上對應點的出度即可。

因為k比較大,矩陣乘法過程中會爆longlong,對於兩個數加起來爆longlong,那麽結果一定是負數,實際結果也就一定大於k,矩乘和求答案時判一下即可。

#include<set>
#include<map>
#include<queue>
#include<stack>
#include<cmath>
#include<bitset>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
ll a[65][125][125];
ll b[125][125];
ll c[125][125];
int mask;
ll ans;
ll K;
int cnt;
int f[45][3];
int v[121];
int n,m;
int x,y,z;
void multiply(ll a[125][125],ll b[125][125],ll c[125][125])
{
    for(int i=0;i<=cnt;i++)
    {
        for(int j=0;j<=cnt;j++)
        {
            c[i][j]=0;
            for(int k=0;k<=cnt;k++)
            {
                if(a[i][k]&&b[k][j])
                {
                    if(a[i][k]<0||b[k][j]<0)
                    {
                        c[i][j]=-1;
                        break;
                    }
                    if(a[i][k]>K/b[k][j])
                    {
                        c[i][j]=-1;
                        break;
                    }
                    c[i][j]+=a[i][k]*b[k][j];
                    if(c[i][j]<0)
                    {
                        c[i][j]=-1;
                        break;
                    }
                }
            }
        }
    }
}
bool check()
{
    ll res=0;
    for(int i=0;i<=cnt;i++)
    {
        if(c[0][i]&&v[i])
        {
            if(c[0][i]<0)
            {
                return 0;
            }
            if(c[0][i]>K/v[i])
            {
                return 0;
            }
            res+=c[0][i]*v[i];
            if(res<0)
            {
                return 0;
            }
        }
    }
    return res<K;
}
int main()
{
    scanf("%d%d%lld",&n,&m,&K);
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=2;j++)
        {
            f[i][j]=++cnt;
        }
    }
    a[0][0][0]++;
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=1;j++)
        {
            a[0][f[i][j]][f[i][j+1]]++;
        }
        a[0][0][f[i][0]]++;
    }
    while(m--)
    {
        scanf("%d%d%d",&x,&y,&z);
        a[0][f[x][z-1]][f[y][0]]++;
        v[f[x][z-1]]++;
    }
    for(mask=0;(1ll<<mask)<=K*3;mask++);
    mask--;
    for(int i=1;i<=mask;i++)
    {
        multiply(a[i-1],a[i-1],a[i]);
    }
    for(int i=0;i<=cnt;i++)
    {
        b[i][i]=1;
    }
    for(int i=mask;i>=0;i--)
    {
        multiply(b,a[i],c);
        if(check())
        {
            ans|=1ll<<i;
            for(int j=0;j<=cnt;j++)
            {
                for(int k=0;k<=cnt;k++)
                {
                    b[j][k]=c[j][k];
                }
            }
        }
    }
    ans++;
    if(ans>K*3)
    {
        ans=-1;
    }
    printf("%lld",ans);
}

BZOJ4386[POI2015]Wycieczki——矩陣乘法+倍增