求S-T的路径中最长边与最短边比值最小的路径。
我不知道怎么做...主要就是这个比值最小,不知道怎么处理,即使看到最小生成树的标签,也没想到咋做......
解题的思想很简单,只要我们确定了最短边的长度,接下来要做的就是使最长边尽量短。最长边尽量短?很自然地就想到了瓶颈生成树,根据Kruscal算法可以求得一颗瓶颈生成树(同样是一颗最小生成树),因为我们总是先尝试短的边。(一颗瓶颈生成树不一定是最小生成树)对于这道题我们并不需要真的求出生成树,只要跑Kruscal当S,T联通就可以了,于是算法就出来了。
1:将边排序。
2:由小到大枚举每条边作为确定的最短边,这条边记为x,因为将边排序了,所以跑Kruscal时可以保证只用比它大的边,当S,T联通时我们停止算法,使S,T联通的这条边记为y。
3:记录最小的w[y]/w[x],就是答案。
虽然根据上述算法能够得到正确答案,算法本身也是正确的,但是有一点是要说明的,我们要求的是S-T路径,而x,y是否一定在S-T路径中?
1:因为y使S,T联通,所以y一定在S-T路径上,上界一定正确。
2:有的x在S-T路径中而有的并不在。
假设y在S-T路径中,下界正确,那么上下界都正确,得到的解是合法的。
假设y不再S-T路径中,又因为y是最短边,所以这个不合法的答案的下界比真实下界小,也就是说在以y作为下界求解的这次过程中,S-T路径上的最短边要比w[y]大,所以这个不合法的解也是不优的。其次,因为不需要这条边依然能使S-T联通,所以我们在之后的枚举过程中,一定能够枚举到真正的下界,使得情况转为1,且得到的解一定比此次的解优。
综上,我们一定能够得到正解。
// q.c #include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<cmath> using namespace std; const int M=500+10; int n,m,s,t,cnt,f[M],ans1=(int)1e9,ans2=1; struct Edge { int u,v,w; Edge():u(0),v(0),w(0) {} bool operator < (const Edge &A) const { return w<A.w; } }ed[M*10]; void add_edge(int a,int b,int c) { ed[++cnt].u=a,ed[cnt].v=b,ed[cnt].w=c; } void reset() { for(int i=1;i<=n;i++) f[i]=i; } int found(int x) { return f[x]==x?x:f[x]=found(f[x]); } int gcd(int a,int b) { return !b?a:gcd(b,a%b); } int main() { freopen("comf.in","r",stdin); freopen("comf.out","w",stdout); scanf("%d%d",&n,&m); reset(); int a,b,c,fx,fy; for(int i=1;i<=m;i++) { scanf("%d%d%d",&a,&b,&c); add_edge(a,b,c); fx=found(a); fy=found(b); if(fx!=fy) f[fx]=fy; } scanf("%d%d",&s,&t); if(found(s)!=found(t)) { printf("IMPOSSIBLE\n"); return 0; } sort(ed+1,ed+cnt+1); for(int i=1;i<=cnt;i++) { reset(); for(int j=i;j<=cnt;j++) { Edge p=ed[j]; fx=found(p.u); fy=found(p.v); if(fx!=fy) f[fx]=fy; if(found(s)==found(t)) { if(ed[j].w*1.0/ed[i].w<ans1*1.0/ans2) { ans1=ed[j].w; ans2=ed[i].w; } break; } } } int d=gcd(ans1,ans2); ans1/=d,ans2/=d; if(ans2==1) printf("%d\n",ans1); else printf("%d/%d\n",ans1,ans2); return 0; }