BZOJ4386[POI2015] Wycieczki

30 篇文章 0 订阅
23 篇文章 1 订阅

BZOJ4386[POI2015] Wycieczki

Description

给定一张n个点m条边的带权有向图,每条边的边权只可能是1,2,3中的一种。

将所有可能的路径按路径长度排序,请输出第k小的路径的长度,注意路径不一定是简单路径,即可以重复走同一个点。

Input

第一行包含三个整数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。

可能有重边。

Output

包含一行一个正整数,即第k短的路径的长度,如果不存在,输出-1。

Sample Input

6 6 11

1 2 1

2 3 2

3 4 2

4 5 1

5 3 1

4 6 3

Sample Output

4

HINT

长度为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。

Solution:

这是一道矩阵乘法的题。

首先来看边权是一的情况:

nn 矩阵中 A 中,Aij表示从 i j的路径条数。设 Ak[i][j] 表示长度为 k 的从i j 的路径条数。显然根据矩阵乘法Am+n=AmAn A1 即是原图。(其实对于图上来说这与倍增Floyd的想法差不多)

要求任意两点之间的路径条数,我们可以添加一个虚拟源点,将它向其他点连边,并给它自己连一条自环(用于矩阵乘法转移)。然后 Ak[0][i] 就能表示以 i 结尾的长度为k的路径条数。

然后来看这一道题:因为矩阵乘法只能适用于边权为1的情况,而它的边权又只是1或2或3,所以我们把一个点拆成三个点,再进行连边,如图:

1

然后我们在这个图上做矩阵乘法(倍增Floyd),可以求出长度为 2k 的路径个数。因为我们拆了点,有些点是虚点,所以把每个点作为最后的倒数第二个点处理,它的贡献就是 A[0][i]cnt[i] ,即以 i <script type="math/tex" id="MathJax-Element-119">i</script>结尾的路径个数乘以它的出度(即将原来的点按出边长度分开处理,可以避免虚点造成的干扰)。

然后就使用倍增的试探方式求出这个答案就可以了。

本题有个神坑:

矩阵乘法(统计答案)的时候有可能爆long long,乘之前应要先判断有没有超过K,否则说明已经超过了K,就标记一下,不更新答案,直接return就可以了。

#include<stdio.h>
#include<string.h>
#include<iostream>
#define ll long long
#define M 125 
#define Exit {res.m[0][0]=-1;return res;}//标记返回 
using namespace std;
void Rd(int &res){
    char c;res=0;
    while(c=getchar(),!isdigit(c));
    do{
        res=(res<<1)+(res<<3)+(c^48);
    }while(c=getchar(),isdigit(c));
}
int n,m,tot,id[M][3],cnt[M];
ll K;
struct Matrix{
    ll m[M][M];
    Matrix(){
        memset(m,0,sizeof(m));
    }
    Matrix operator *(const Matrix &A)const{
        Matrix res;
        for(int i=0;i<=tot;i++)
            for(int j=0;j<=tot;j++)
                if(m[i][j]<0||A.m[i][j]<0)Exit;
        for(int i=0;i<=tot;i++)
            for(int j=0;j<=tot;j++)
                if(m[i][j]){
                    for(int k=0;k<=tot;k++)
                        if(A.m[j][k]){
                            if(m[i][j]>K/A.m[j][k])Exit;
                            res.m[i][k]+=m[i][j]*A.m[j][k];
                            if(res.m[i][j]>K)Exit;
                        }
                }
        return res;
    }
}a[65],b,c;
bool check(){
    if(b.m[0][0]<0)return false;
    ll k=0;
    for(int i=1;i<=tot;i++){
        if(b.m[0][i]&&cnt[i]){
            if(b.m[0][i]<0)return false;
            if(b.m[0][i]>K/cnt[i])return false;
            k+=b.m[0][i]*cnt[i];
            if(k>=K)return false;
        }
    }
    return k<K;
}
int main(){
    Rd(n);Rd(m);cin>>K;
    for(int i=1;i<=n;i++)
        for(int j=0;j<3;j++)
            id[i][j]=++tot;
    for(int i=1;i<=n;i++){
        for(int j=0;j<2;j++)
            a[0].m[id[i][j]][id[i][j+1]]++;
        a[0].m[0][id[i][0]]++;
    }
    a[0].m[0][0]++;
    while(m--){
        int x,y,z;
        Rd(x);Rd(y);Rd(z);
        z--;
        cnt[id[x][z]]++;
        a[0].m[id[x][z]][id[y][0]]++;
    }
    ll Max=K*3,ans=0;
    int len=0;
    while((1LL<<len)<=Max)len++;
    for(int i=1;i<len;i++)
        a[i]=a[i-1]*a[i-1];
    c.m[0][0]=1;
    for(int i=len-1;i>=0;i--){
        b=c*a[i];
        if(check()){
            ans|=(1LL<<i);
            memcpy(c.m[0],b.m[0],sizeof(b.m[0]));
        }
    }
    ans++;
    if(ans>=Max)ans=-1;
    cout<<ans<<endl;
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值