描述php
在Byte山的山脚下有一个洞穴入口. 这个洞穴由复杂的洞室通过隧道链接构成. 洞穴的入口是一条笔直通向“前面洞口”的道路. 隧道互相都不交叉(他们只在洞室相遇). 两个洞室要么就经过隧道链接起来,要么就通过若干隧道间接的相连. 如今决定组织办一个'King's of Byteotia Cup' 比赛. 参赛者的目标就是任意选择一条路径进入洞穴并尽快出来便可. 一条路径必须通过除了“前面洞口”以外还至少要通过其余一个洞室.一条路径中一个洞不能重复通过(除了“前面洞室”之外),相似的一条隧道也不能重复通过. 一个著名的洞穴探险家 Byteala 正准备参加这个比赛. Byteala 已经训练了数月并且他已得到了洞穴系统的一套详细资料. 对于每条隧道他都详细计算了从两个方向通过所须要的时间. 通过一个洞室的时间很短能够忽略不记. 如今Byteala 向计算一条符合条件的最优路径.c++
输入算法
第一行有两个数n 和 m (3 <= n <= 5000, 3 <= m <= 10000) 分别表示洞室的数目以及链接他们的隧道的数目. 洞室从1 到 n编号. “前面洞室”的编号为1. 接下来m 行描述了全部的隧道. 每行四个整数a,b,c,d 表示从洞室a到洞室b须要c分钟的时间,而从洞室b到洞室a须要d分钟的时间, 1 <= a,b <= n, a <> b, 1 <= c,d <= 10000. 你能够假设符合要求的路径确定存在.数组
输出flex
输出一行,最少须要多少时间完成比赛.优化
输入样例 1spa
3 3 1 2 4 3 2 3 4 2 1 3 1 1
输出样例 1blog
6
来源get
[POI2004]input
给你们提供一个测评地点吧:https://www.luogu.org/problemnew/show/T79047,你们也能够加入个人团队。数据均随机生成,生成数据的代码为:
#include <cstdio> #include <ctime> #include <algorithm> using namespace std; const int MAXN=10000; int n,m,k; int main() { srand((unsigned)time(NULL)); n=rand()%4997+13;m=rand()%9997+3; printf("%d %d\n",n,m); k=rand()%(m/2)+2; for (int i=1;i<=k;i++) { if (rand()%2==1) printf("1 %d %d %d\n",rand()%n+1,rand()%MAXN+1,rand()%MAXN+1); else printf("%d 1 %d %d\n",rand()%n+1,rand()%MAXN+1,rand()%MAXN+1); } int u,v; for (int i=k+1;i<=m;i++) { u=rand()%n+1;v=rand()%n+1; while (v==n)v=rand()%n+1; printf("%d %d %d %d\n",u,v,rand()%MAXN+1,rand()%MAXN+1); } return 0; }
这个题目的意思是,从洞口$1$进入,通过其余至少$1$个洞口后,再从洞口$1$出来,每一个洞口只能通过一次(洞口$1$除外)。求最短路径。
这道题目因为起点和终点同样,咱们通常的$SPFA$、$dijkstra$等等都无论用了,更可恶的是,因为每条边方向不一样,权值也不一样,这使得$Floyd$也很差用了。那么,咱们该怎么办呢?
咱们要把它转化为求普通的最短路!
咱们把与顶点$1$相连的点记录下来,分别求最短路,而后再加上到点1的距离就好了。因而,你会惊奇的发现$Time~Limit~Exceeded$。那怎么办呢??
咱们能够把与$1$相连的点分为两组,一下求这么多的最短路。对与分组,二进制枚举就能够了。
AC代码:
#include <queue> #include <cstdio> #include <bitset> #include <cstring> using namespace std; int read() { int x=0,f=1;char c=getchar(); while (c<'0' || c>'9'){if (c=='-')f=-1;c=getchar();} while (c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-48;c=getchar();} return x*f; } const int MAXN=5005; const int MAXM=20005; int n,m,c,id,now,fir; struct Qu { int dot,dis; bool operator < (const Qu tmp) const { return dis>tmp.dis; } }; struct edge { int v,w,nx; }set[MAXM],key[MAXN]; int head[MAXN],dis[MAXN]; bitset<MAXN> vis; priority_queue<Qu> Q; inline void Addedge(int u,int v,int w) { id++;set[id].v=v;set[id].w=w;set[id].nx=head[u]; head[u]=id; } inline void insert_edge(int v,int w) { now++;key[now].v=v;key[now].w=w;key[now].nx=fir; fir=now; } inline void init() { int u,v,a,b; n=read();m=read(); for (int i=1;i<=m;i++) { u=read();v=read();a=read();b=read(); Addedge(u,v,a);Addedge(v,u,b); if (u==1)insert_edge(v,b); if (v==1)insert_edge(u,a); } int x=n; while (x>0) {c++;x/=2;} } inline void dijkstra() { vis.reset();vis.set(1); for (int k=head[1];k>0;k=set[k].nx) Q.push((Qu){set[k].v,dis[set[k].v]}); int u,v; while (!Q.empty()) { u=Q.top().dot;Q.pop(); vis.set(u); for (int k=head[u];k>0;k=set[k].nx) { v=set[k].v; if (dis[u]+set[k].w<dis[v]) { dis[v]=dis[u]+set[k].w; if (!vis[v])Q.push((Qu){v,dis[v]}); } } } } int main() { init(); int ans=0x3f3f3f3f; for (int i=(1<<c);i>0;i>>=1) { memset(dis,0x3f,sizeof(dis)); for (int k=head[1];k>0;k=set[k].nx) if (set[k].v&i)dis[set[k].v]=set[k].w; dijkstra(); for (int k=fir;k>0;k=key[k].nx) if (~key[k].v&i)ans=min(ans,dis[key[k].v]+key[k].w); memset(dis,0x3f,sizeof(dis)); for (int k=head[1];k>0;k=set[k].nx) if (~set[k].v&i)dis[set[k].v]=set[k].w; dijkstra(); for (int k=fir;k>0;k=key[k].nx) if (key[k].v&i)ans=min(ans,dis[key[k].v]+key[k].w); } printf("%d\n",ans); return 0; }
而后,咱们须要证实这个方案的正确性。
首先,咱们很容易想到初版无脑枚举,就是枚举起点和终点,这样的话,枚举的复杂度是$(n*n)$,再加上$dijkstra$的时间复杂度$O(n*n)$,总的时间复杂度就是$O(n^4)$。但这种方法能够$TLE$飞。因而,便要进行一点优化:先枚举起点,求一遍最短路并经过$dis_i$数组记录从起点到点$i$的距离,时间复杂度即是$O(n^3)$,效率是有一点提升,但本题的数据实在是太坑了,这个算法也被卡掉了。
因此还要找更加优化的算法。咱们能够把数字的二进制列出来:
十进制数 | 二进制数 |
$1$ | $1$ |
$2$ | $10$ |
$3$ | $11$ |
$4$ | $100$ |
$5$ | $101$ |
$6$ | $110$ |
$7$ | $111$ |
$8$ | $1000$ |
$.~.~.$ | $.~.~.$ |
能够发现,每一个数至少有一个位上的数的差异。咱们能够 枚举$1$的位置,这样一来,咱们能够把此位为$1$的点分到$A$组,为零的点分到$B$组,从$A$组出发求最短路,距离就是点$1$到他们的距离。因而枚举的复杂度便变为了$(log^2~n)$,因而总的时间复杂度为$O(log^2~n*n^2)$。