【poj3613】Cow Relays

【题意】

给定一个含T条边的无向图,求从s到e的恰好经过n条边的最短路(可以经过重复边)。

【题解】

我们先来回想一下floyd。它的本质实际上是DP

f [ k ] [ i ] [ j ] f[k][i][j] f[k][i][j]为从i开始,经过1~k的某些点到达j的最短路的总权值,则:
f [ k ] [ i ] [ j ] = m i n { f [ k − 1 ] [ i ] [ k ] + f [ k − 1 ] [ k ] [ j ] } f[k][i][j]=min\{f[k-1][i][k]+f[k-1][k][j]\} f[k][i][j]=min{f[k1][i][k]+f[k1][k][j]}
降维可得:
f [ i ] [ j ] = m i n { f [ i ] [ k ] + f [ k ] [ j ] } f[i][j]=min\{f[i][k]+f[k][j]\} f[i][j]=min{f[i][k]+f[k][j]}
同时,这也是为什么要先枚举k的原因。

那么,按照这个思路,我们将它改一改。设 f [ t ] [ i ] [ j ] f[t][i][j] f[t][i][j]为从 i i i j j j经过 t t t个中间点的最短路, G [ i ] [ j ] G[i][j] G[i][j]为图初始的邻接矩阵。则:
f [ t ] [ i ] [ j ] = m i n { f [ t − 1 ] [ i ] [ k ] + g [ k ] [ j ] } f[t][i][j]=min\{f[t-1][i][k]+g[k][j]\} f[t][i][j]=min{f[t1][i][k]+g[k][j]}
目测要T。接下来考虑优化。
我们先把 f f f的第一维去掉,而是用多个数组来表示不同状态下的 f [ i ] [ j ] f[i][j] f[i][j],记作 d t [ i ] [ j ] d_t[i][j] dt[i][j]。则原方程可转化为:
d t [ i ] [ j ] = m i n { d t − 1 [ i ] [ k ] + g [ k ] [ j ] } d_t[i][j]=min\{d_{t-1}[i][k]+g[k][j]\} dt[i][j]=min{dt1[i][k]+g[k][j]}
再想一想,我们还可以把它改写成下列形式:
d t [ i ] [ j ] = m i n { d t ′ [ i ] [ k ] + d t − t ′ [ k ] [ j ] } d_t[i][j]=min\{d_{t'}[i][k]+d_{t-t'}[k][j]\} dt[i][j]=min{dt[i][k]+dtt[k][j]}
我们发现,其实这个方程的转移是满足结合律的。所以,在优化的时候,我们可以像矩阵快速幂一样,对方程的转移进行二进制优化。
仿照矩阵快速幂,我们可以像这样定义新乘法:

#define ll long long
struct matrix{
	ll a[max_n][max_n];
	matrix(){memset(a, 0x3f, sizeof a);}
	matrix operator *(const matrix b) const
	{
		matrix c;
		for(int i = 1; i <= n; i++)
			for(int j = 1; j <= n; j++)
				for(int k = 1; k <= n; k++)
					c.a[i][j] = min(c.a[i][j], a[i][k] + b.a[k][j];
		return c;
	}
};

这篇博客里提到:

G n [ i ] [ j ] G^n[i][j] Gn[i][j]表示从i走n步到j的方案数

于是,我们对原邻接矩阵做n次上面的奇怪的乘法,就可以得到答案。
由于边数顶破天100条而编号可能很大,所以我们可以对点的编号离散化。

【代码】
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int mn = 205, mm = 1005;
int cnt;
struct matrix{
    int a[mn][mn];
    matrix() {memset(a, 0x3f, sizeof a);}
    matrix operator* (const matrix x) const
    {
        matrix c;
        for(int i = 1; i <= cnt; i++)
            for(int j = 1; j <= cnt; j++)
                for(int k = 1; k <= cnt; k++)
                    c.a[i][j] = min(c.a[i][j], a[i][k] + x.a[k][j]);
        return c;
    }
}ret, h;
struct edge{
    int a, b, w;
}e[mm];
bool vis[mm];
int val[mm];
inline void ksm(int m)
{
    while(m)
    {
        if(m & 1)
            ret = ret * h;
        h = h * h, m >>= 1;
    }
}
int main()
{
    int n, m, s, t, i;
    scanf("%d%d%d%d", &m, &n, &s, &t);
    for(i = 1; i <= n; i++)
        scanf("%d%d%d", &e[i].w, &e[i].a, &e[i].b), vis[e[i].a] = vis[e[i].b] = 1;
    for(i = 1; i <= 1000; i++)
        if(vis[i])
            val[i] = ++cnt;
    for(i = 1; i <= n; i++)
    {
        int a = val[e[i].a], b = val[e[i].b];
        h.a[a][b] = h.a[b][a] = e[i].w;
    }
    for(i = 1; i <= cnt; i++)
        ret.a[i][i] = 0;	//这里最开始的矩阵的0次方表示从i至j走0步的最短路,所以除了ret.a[i][i]=0外全部取正无穷
    ksm(m);
    printf("%d\n", ret.a[val[s]][val[t]]);
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值