HDU 6071(同余最短路)

传送门:

题意:

给你四个点,相邻两个点之间有一条连边。现在问你从 2 2 2号点走到 2 2 2号点的至少为 K K K的最短路的长度。

题意:

一个非常有意思的算法!

因为题目中的 K K K比较大,因此一些乱搞的算法显然无法通过,而鉴于点数相对来说比较少,因此我们得研究一下这几个点所蕴涵的性质。

我们发现,对于 2 2 2号结点,它有两条可行的边,我们设这两条边的距离分别为 d 12 d_{12} d12以及 d 23 d_{23} d23,其中最小的边为 w w w,(即 w = min ⁡ ( d 12 , d 23 ) w=\min(d_{12},d_{23}) w=min(d12,d23))。如果要从 2 2 2号结点走回到 2 2 2号结点,一个显然优的策略是我们至少要访问两次 w w w边。即答案至少为 2 ∗ w 2*w 2w

假设我们当前从 2 2 2 2 2 2的最短路为 d i d_i di,则我们显然可以进行 t t t次上述的操作,使得最终的答案满足 d i + 2 ∗ t ∗ w ≥ K d_i+2*t*w \ge K di+2twK。显然这是一个线性的方程组,参数为 d i d_i di以及 t t t。而我们要知道 t t t的大小,显然只需要知道 d i % 2 w d_i \%2w di%2w的值即可。

因此,这个问题其实是一个线性同余的问题,因为最终的答案跟 d i % 2 w d_i \%2w di%2w有关,我们可以设 d i s [ i ] [ j ] dis[i][j] dis[i][j],为从起点到 i i i点,最短路径 % 2 w \%2w %2w j j j的最短路长度。此时的状态数就直接被我们缩短为 2 ∗ w ∗ 4 2*w*4 2w4个,因此此时我们直接可以用最短路算法去求解了。

最后我们只需要不断枚举 d i s [ 2 ] [ j ] dis[2][j] dis[2][j],判断求出最优的 t t t以及 d i d_i di即可

时间复杂度: O ( w l o g w ) \mathcal{O}(wlogw) O(wlogw)

代码:

#include <bits/stdc++.h>
#define maxn 10
using namespace std;
typedef long long ll;
typedef pair<ll,int>pll;
struct Node{
    int to,next,cost;
}q[10];
const ll INF=0x3f3f3f3f3f3f3f3f;
int head[maxn],cnt=0;
ll dis[10][100005],d;
void add_edge(int from,int to,int cost){
    q[cnt].next=head[from];
    q[cnt].to=to;
    q[cnt].cost=cost;
    head[from]=cnt++;
}
void dijk(int x){
    priority_queue<pll,vector<pll>,greater<pll> >que;
    for(int i=0;i<=4;i++){
        for(int j=0;j<=d;j++) dis[i][j]=INF;
    }
    dis[x][0]=0;
    que.push(pll(0LL,x));
    while(!que.empty()){
        pll p=que.top();
        que.pop();
        x=p.second;
        if(p.first>dis[x][p.first%d]) continue;
        for(int i=head[x];i!=-1;i=q[i].next){
            int to=q[i].to;
            ll dd=q[i].cost+dis[x][p.first%d];
            if(dis[to][dd%d]>dd){
                dis[to][dd%d]=dd;
                que.push(pll(dd,to));
            }
        }
    }
}
int val[10];
int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        ll k;
        scanf("%lld",&k);
        memset(head,-1,sizeof(head));
        cnt=0;
        d=0;
        for(int i=1;i<=4;i++){
            scanf("%d",&val[i]);
            d=max(1ll*val[i],d);
        }
        for(int i=1;i<=4;i++){
            add_edge(i,i%4+1,val[i]);
            add_edge(i%4+1,i,val[i]);
        }
        d*=2;
        dijk(2);
        ll minn=INF;
        for(int i=0;i<=d;i++){
            if(dis[2][i]==INF) continue;
            if(dis[2][i]>=k){
                minn=min(dis[2][i],minn);
            }
            else{
                ll tmp=k-dis[2][i];
                minn=min(minn,dis[2][i]+tmp/d*d+(i>0)*d);
            }
        }
        printf("%lld\n",minn);
    }
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值