【bzoj4386】[POI2015]Wycieczki 矩阵乘法

题目描述

给定一张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的话,很容易想到使用矩阵乘法解决。把邻接矩阵的$2^i$记录,直到路径条数足够为止。此时需要统计的是路径长度小于等于某数的路径数,因此新建一个计数器,每个点向它连边,然后计数器连一个自环即可。然后按照倍增LCA的方法按$i$从大到小处理即可。

那如果边长为2或3呢?可以考虑拆点解决(不能拆边因为边数过多,无法矩乘)。对于每个点再建两个辅助节点并连边,分别代表长度为2、3,长度为2时从第一个辅助节点连出去,长度为3时从第二个辅助节点连出去,即可得到答案。

这里嘴上说着真容易= =

事实上,本题会爆long long特别多。因为边数是点数的指数级别的。所以判路径是否超过k时很复杂,正解的话需要在矩乘里面写,外面判断时也要写,非常的复杂。

好在本题数据弱,因此不需要在矩乘里面写,直接在外部判断即可。

时间复杂度$O(n^3\log k)$。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
ll m;
int n;
struct data
{
	ll v[121][121];
	ll* operator[](int a) {return v[a];}
	data operator*(data &a)const
	{
		int i , j , k;
		data ans;
		for(i = 0 ; i <= 3 * n ; i ++ )
			for(j = 0 ; j <= 3 * n ; j ++ )
				ans[i][j] = 0;
		for(i = 0 ; i <= 3 * n ; i ++ )
			for(j = 0 ; j <= 3 * n ; j ++ )
				for(k = 0 ; k <= 3 * n ; k ++ )
					ans[i][j] += v[i][k] * a[k][j];
		return ans;
	}
	bool check()
	{
		ll ret = 0;
		int i;
		for(i = 1 ; i <= n ; i ++ )
			if((ret += v[i][0] - 1) >= m)
				return 1;
		return 0;
	}
}A[70] , Now , Tmp;
bool judge(data &x , data &y , data &z)
{
	data tmp = x * y;
	if(tmp[0][0] == -1) return 0;
	int i;
	ll ret = 0;
	for(i = 1 ; i <= n ; i ++ )
		if((ret += tmp[i][0]) >= m)
			return 0;
	z = tmp;
	return 1;
}
int main()
{
	int q , i , x , y , z;
	ll ans = 0;
	scanf("%d%d%lld" , &n , &q , &m);
	A[0][0][0] = 1;
	for(i = 1 ; i <= n ; i ++ ) Now[i][i] = A[0][i][0] = A[0][i][i + n] = A[0][i + n][i + 2 * n] = 1;
	for(i = 1 ; i <= q ; i ++ ) scanf("%d%d%d" , &x , &y , &z) , A[0][x + (z - 1) * n][y] ++ ;
	for(i = 1 ;  ; i ++ )
	{
		if(i > 65)
		{
			puts("-1");
			return 0;
		}
		A[i] = A[i - 1] * A[i - 1];
		if(A[i].check()) break;
	}
	for(i -- ; ~i ; i -- )
	{
		Tmp = Now * A[i];
		if(!Tmp.check()) Now = Tmp , ans += (1ll << i);
	}
	printf("%lld\n" , ans);
	return 0;
}

 

 

转载于:https://www.cnblogs.com/GXZlegend/p/7481404.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值