[51nod1326]遥远的旅途

本文探讨了一种特殊的图论问题,即寻找从起点到终点路径长度恰好为T的路径,允许点和边重复使用。文章提出了一种动态规划算法,通过枚举环路并维护路径长度的状态来解决此问题,并提供了完整的代码实现。

题目大意

给你一个n个点m条边的无向图,每条边有正整数的边权,问是否存在一条0到n-1的长度为T的路径(点和边可以重复)。

n,m≤50 边权不超过10000 T≤1018
数据组数不超过3

分析

这道题有点考思维啊!
考虑这样的一条路径。如果不是简单路径,它可能会包括若干个环。
如果我确定了一个必须走的环,假设它的长度为len,那么可以这样设状态:
f[i][j][0/1]表示:走到了i这个点,路径的长度模len等于j,0/1表示我是否走过这个环。f[i][j][k]记录的是满足i,j,k的最短路径长度。然后答案就是判断f[n-1][T%len][1]是否小于等于T。

证明上面的算法的正确性:假设我在路径上有另一个环,那么假设第一次走进这个环是路径长度模len为j,走这个环若干次后,第二维状态一定会回到j。多个环也类似。

所以剩下的就是枚举可能的环了。发现单独一条边也可以构造出一个环,而且边权不是很大,所以可以考虑枚举所有边。同时,修改第三维01状态表示这条边是否经过(即不一定要走环),正确性没有变。这样就可以通过所有数据了。

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N=55,M=20005;

typedef long long LL;

LL Len,f[N][M][2];

int Case,n,m,h[N],e[N*2],nxt[N*2],E[N][3],tot,D[N*2],Data[N*M][3];

bool v[N][M][2];

void add(int x,int y,int len)
{
    e[++tot]=y; nxt[tot]=h[x]; h[x]=tot; D[tot]=len;
}

bool check(int p)
{
    int mo=E[p][2]*2;
    memset(v,0,sizeof(v));
    for (int i=0;i<n;i++) for (int j=0;j<mo;j++) f[i][j][0]=f[i][j][1]=Len+1;
    Data[tot=1][0]=Data[1][1]=Data[1][2]=0;
    f[0][0][0]=0;
    for (int i=0;i!=tot;)
    {
        i=i%(N*M)+1;
        int x=Data[i][0],y=Data[i][1],z=Data[i][2];
        for (int j=h[x];j;j=nxt[j])
        {
            int l=(y+D[j])%mo,
            bz=((x==E[p][0] && e[j]==E[p][1] || x==E[p][1] && e[j]==E[p][0])&&D[j]==E[p][2])?1:z;
            if (f[x][y][z]+D[j]<f[e[j]][l][bz])
            {
                f[e[j]][l][bz]=f[x][y][z]+D[j];
                if (!v[e[j]][l][bz])
                {
                    v[e[j]][l][bz]=1;
                    tot=tot%(N*M)+1;
                    Data[tot][0]=e[j]; Data[tot][1]=l; Data[tot][2]=1;
                }
            }
        }
        v[x][y][z]=0;
    }
    return f[n-1][Len%mo][1]<=Len;
}

void work()
{
    memset(h,0,sizeof(h));
    tot=0;
    scanf("%d%d%lld",&n,&m,&Len);
    for (int i=0;i<m;i++)
    {
        scanf("%d%d%d",&E[i][0],&E[i][1],&E[i][2]);
        add(E[i][0],E[i][1],E[i][2]); add(E[i][1],E[i][0],E[i][2]);
    }
    for (int i=0;i<m;i++)
        if (check(i))
        {
            printf("Possible\n"); return;
        }
    printf("Impossible\n");
}

int main()
{
    for (scanf("%d",&Case);Case--;work());
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值