简述题意:给你一个由四个节点组成的环,相邻两点间可达,求从节点2出发,回到节点2的不小于k的最短路径的长度。
算法:同余最短路
难度:NOIP
题解:
假设我们将任意一条长度大于k的回路(从2出发回到2)为可行路径,那么任意一条可行路径加上2w一定还是可行路径,所有可行方案中,最短的是k,最长的为 k + 2 * n * w,(n趋近正无穷),(因为我们可以无限循环地走)显然我们不可能求出所有的可行路径来,由于所有可行路径长度中都含有i*2w(i=0,1,2...),我们可以考虑按对2w的余数分块(枚举),这样我们只要求出%2w == 0的最短可行路径,%2w == 1的最短可行路......一直到%2w == 2w - 1的最短可行路径,然后在这2w条可行路径中找一条最短的就是答案了(因为这条一定是所有可行路径中最短的一条)
然后问题就是找到求出%2w同余的所有可行路径中最短的那条了,用dis[i][j]表示从2号点出发到达i,长度%2w为j的最短路径的长度(dis储存的不一定是合法方案,长度可以小于K,因为我们可以最后加x个2w使其刚好大于等于K,并且添加2w以后并不会改变其长度%2w == j 的性质),这个数组可以用dijkstra(spfa)(dijspfa)的算法求出。
时间复杂度:
代码如下:
#include
#include
#include
#include
#include
#include
#include
#define ll long long
#define N 15
using namespace std;
ll k;
struct node
{
int next;
int to;
int val;
}edg[N<<1];
int hea[N];
ll dis[5][60005];
int cnt=1;
void init()
{
memset(hea,-1,sizeof(hea));
cnt=1;
}
void add(int u,int v,int w)
{
edg[cnt].next=hea[u];
edg[cnt].to=v;
edg[cnt].val=w;
hea[u]=cnt++;
}
struct nod
{
int po;
ll d;
};
bool operator < (nod x,nod y)
{
return x.d>y.d;
}
int mm;
priority_queueQ;
void dijspfa(int rt)
{
memset(dis,0x3f3f3f3f,sizeof(dis));
nod tem;
tem.po=rt;
tem.d=0ll;
Q.push(tem);
while(!Q.empty())
{
nod op;
op=Q.top();
Q.pop();
int u=op.po;
ll w=op.d;
if(w>dis[u][w%mm]) continue;
for(int i = hea[u];i != -1;i=edg[i].next)
{
int to=edg[i].to;
ll ww=w+edg[i].val;
if(dis[to][ww%mm]>ww)
{
dis[to][ww%mm]=ww;
nod ee;
ee.po=to;
ee.d=ww;
Q.push(ee);
}
}
}
}
int main()
{
int T,d1,d2,d3,d4;
scanf("%d",&T);
while(T--)
{
init();
scanf("%I64d%d%d%d%d",&k,&d1,&d2,&d3,&d4);
mm=max(d1,d2)*2;
add(1,2,d1),add(2,1,d1);
add(2,3,d2),add(3,2,d2);
add(3,4,d3),add(4,3,d3);
add(4,1,d4),add(1,4,d4);
dijspfa(2);
ll ans=0x7fffffffffffffffll;
for(int i = 0;i < mm;i++)
{
ll dta=k-dis[2][i];
if (dta<=0) ans=min(ans,dis[2][i]);
else ans=min(ans,dis[2][i]+dta/mm*mm+(dta%mm>0)/*如果为成立,就是1,反之则是0*/*mm);
}
printf("%I64d\n",ans);
}
return 0 ;
}