取模最短路

xiaoxiao2021-02-28  114

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)); } }
转载请注明原文地址: https://www.6miu.com/read-21831.html

最新回复(0)