取模最短路

hdu 6071
题意:图上有4个点,给定一个k求从2点出发最后回到2这个点且距离不小于k的最短距离。
思路:题目上说的从2出发最后又回到2,所以这是一个回路,而两个回路可以形成一个新的回路,假如现在有两个回路,长度分别为a和b则可以形成新的回路长度a+n*b。题目上让求不小于k的最短路又是回路,所以我们可以找到从2点出发的最小回路将它看做前边提到的b则,我们只需要找到最小的a就可以找到最优解了,而b显而易见的解是min(dis12,dis23)。但是这样找什么时候是终点呢,因为回路是无限的。但是我们可以想到假如前面的a%b的值相同那么上面的解的值就是相同的,所以我们只需要找到%b的非负完全剩余系里面的最小值即可。
比如回到2点的时候有长度为3的和长度为5的两条路径,现在最短的回路长度为2,我们就可以在长度为3的时候再走一遍最短回路形成长度为5的回路,所以只需要记录长度为3就好了。
而我们就可以用最短路来实现,相当于将一个点拆分成了2*b个点,每个代表一个2*b的完全非负剩余系的数。

#include<cstdio>
#include<queue>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
const int maxn=1e5+10;
const LL inf=1e17+8e16;
int head[maxn],cont,vis[5][maxn];
LL dis[5][maxn],mod;
/*
dis[i][j]数组存的是到达i点且长度对最短回路取余为j的最短回路长度
vis代表的和dis数组一样但是是用来标记这种状态是否在队列中的
*/
struct zp
{
    int u,v,next;
    LL w;
}node[maxn];
void init()
{
    cont=0;
    memset(head,-1,sizeof(head));
    memset(vis,0,sizeof(vis));
}
void add(int u,int v,LL w)//双向边
{
    node[cont].u=u,node[cont].v=v,node[cont].w=w;
    node[cont].next=head[u];
    head[u]=cont++;
    node[cont].u=v,node[cont].v=u,node[cont].w=w;
    node[cont].next=head[v];
    head[v]=cont++;
}
struct edge
{
    int id,dis;
    edge(int _id,int _dis)
    {
        id=_id,dis=_dis;
    }
};
void dij()//最短路算法
{
    queue<edge> q;
    q.push(edge(2,0));
    dis[2][0]=0;
    while(!q.empty())
    {
        int u=q.front().id;
        int num=q.front().dis;
        q.pop();
        vis[u][num]=0;
        for(int i=head[u];i!=-1;i=node[i].next)
        {
            int v=node[i].v;
            LL w=node[i].w;
            int tmp=(dis[u][num]+w)%mod;//到达v点余数为tmp
            if(dis[v][tmp]>dis[u][num]+w)//更新最小值
            {
                dis[v][tmp]=dis[u][num]+w;
                if(!vis[v][tmp])//标记
                {
                    q.push(edge(v,tmp));
                    vis[v][tmp]=1;
                }
            }
        }
    }
}
int main()
{
    int ncase;
    scanf("%d",&ncase);
    while(ncase--)
    {
        init();
        LL k,a,b,c,d;
        scanf("%lld%lld%lld%lld%lld",&k,&a,&b,&c,&d);
        add(1,2,a),add(2,3,b),add(3,4,c),add(4,1,d);
        mod=2*min(a,b);//最短回路
        for(int i=1;i<=4;i++)
            for(int j=0;j<mod;j++)
            dis[i][j]=inf;
        dij();
        LL ans=((k-1)/mod+1)*mod;//直接走最短回路的解
        for(int i=0;i<mod;i++)//枚举余数找最优解
        {
            if(dis[2][i]>=k) ans=min(ans,dis[2][i]);
            else ans=min(ans,((k-dis[2][i]-1)/mod+1)*mod+dis[2][i]);
        }
        printf("%lld\n",ans);
    }
}

bnu oj商品装箱
这里有一个类似的题目和解法
思路:和上一道题同样的思想,这一是一个无限的构造,每个数都可以用无数次,但是对其中某个数a取余确实有限的,只要余数相同就可大的数就可以用最小的数构造出来所以我们可以求出关于一个数的所有余数的最小花费,及到达某个余数构造出来的最小数,这就可以根据余数之间的关系来建立最短路,路上的花费就是这个数,两端的节点就是不同的余数。
这样跑一边最短路就可以知道有哪些余数可以达到,有哪些不可以达到。

#include<cstdio>
#include<cstring>
#include<queue>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1e6+10;
const int inf=0x3f3f3f3f;
int x[maxn];
struct zp
{
    int u,v,w,Next;
} node[maxn];
int head[maxn],cont;
void init()
{
    memset(head,-1,sizeof(head));
    cont=0;
}
void add(int u,int v,int w)
{
    node[cont].u=u,node[cont].v=v,node[cont].w=w;
    node[cont].Next=head[u];
    head[u]=cont++;
}
int SPFA()
{
    int n=x[0];
    int dis[10010],vis[10010];
    memset(dis,0x3f3f,sizeof(dis));
    memset(vis,0,sizeof(vis));
    queue<int> q;
    q.push(0);
    dis[0]=0;
    vis[0]=1;
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        vis[u]=0;
        for(int i=head[u];i!=-1;i=node[i].Next)
        {
            int v=node[i].v,w=node[i].w;
                if(dis[u]+w<dis[v])
                {
                    dis[v]=dis[u]+w;
                    if(vis[v]==0)
                        q.push(v);
                }
        }
    }
    int Max=-1;
    for(int i=0;i<n;i++)
    Max=max(Max,dis[i]);
    if(Max==0||Max==inf) return -1;//如果有不可达到的余数就不可能有最小值
    else return Max-n+1;//返回最小值,代表最小值后面的都可以被构造出来
}
int solve(int n)
{
    int z=x[0];//选最小的数
    int b[10010],a[10010];
    int tot=0;
    memset(b,0,sizeof(b));
    for(int i=0;i<n;i++)
    {
        int tmp=x[i]%z;
        if(!b[tmp])//将余数不同的记录起来
        {
            a[tot++]=x[i];
            b[tmp]=1;
        }
    }
    for(int i=0;i<z;i++)//建图
    {
        for(int j=0;j<tot;j++)//根据余数之间的关系
        {
            if(i!=(i+a[j])%z)//余数i可以通过+a[j]来到达余数tmp
            {
                int tmp=(i+a[j])%z;
                add(i,tmp,a[j]);
            }
        }
    }
    return SPFA();
}
int main()
{
    int ncase;
    scanf("%d",&ncase);
    while(ncase--)
    {
        init();
        int n;
        scanf("%d",&n);
        for(int i=0; i<n; i++)
            scanf("%d",&x[i]);
        sort(x,x+n);
        if(x[0]==1)
            printf("1\n");
        else
        printf("%d\n",solve(n));
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值