P2502 [HAOI2006]旅行
written on 2022-05-25
这篇题解的思考历程分为两部分。
part 1
读完题后,我们容易知道答案具有单调性,因此考虑二分答案。
对于这样两个端点均不固定的题目,我们通常采用的方式是:固定一段,二分另一端。那么对于这题而言,我们可以考虑枚举最大值,二分最小值。至于判定,用一个 dfs 或是略有修改的 SPFA(伪)均可以做到
这种方法的总时间复杂度为 实测不可过,需要优化。
这里有三处优化:
-
对于不可行的情况单独计算,这样就避免了跑满的情况,防止时间爆炸。
-
可以考虑二分的细节,在枚举最小值时,我们不必逐个数字枚举,可以先将所有的边按边权从小到大排序,然后直接枚举边的下标。
-
最外层的最大值枚举是有序的,因此二分下界也是有序的,以此为据进行优化。
这样开了O2就能过了
#include<bits/stdc++.h>
#define N 505
#define M 5005
using namespace std;
int n,m,mx,mn;
int tot,ver[M<<1],nxt[M<<1],edge[M<<1],head[N];
void add_E(int x,int y,int z){ver[++tot]=y,edge[tot]=z,nxt[tot]=head[x],head[x]=tot;}
int p[M],ansx=-1,ansy=1;
int gcd(int a,int b){return b?gcd(b,a%b):a;}
bool mark[N];
int s,t;
bool check()
{
queue<int> q;
q.push(s);
for(int i=1;i<=n;i++) mark[i]=0;
mark[s]=1;
while(q.size())
{
int x=q.front();
q.pop();
for(int i=head[x];i;i=nxt[i])
{
int y=ver[i],z=edge[i];
if(mark[y]||z>mx||z<mn) continue;
if(y==t) return 1;
q.push(y);
mark[y]=1;
}
}
return 0;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add_E(x,y,z),add_E(y,x,z);
p[i]=z;
}
scanf("%d%d",&s,&t);
mx=1e9,mn=0;
if(!check()) puts("IMPOSSIBLE"),exit(0);
sort(p+1,p+1+m);
int lst=1;
for(int i=1;i<=m;i++)
{
mx=p[i];
int l=max(lst,1),r=i,res=-1;
while(l<=r)
{
int mid=l+r>>1;
mn=p[mid];
if(check()) l=mid+1,res=mid;
else r=mid-1;
}
lst=res;
if(res==-1) continue;
mn=p[res];
double now=mx*1.0/mn,Ans=ansx*1.0/ansy;
if(now<Ans||ansx==-1) ansx=mx,ansy=mn;
}
int g=gcd(ansx,ansy);
if(ansx%ansy==0) printf("%d\n",ansx/g);
else printf("%d/%d\n",ansx/g,ansy/g);
}
//O(m*logm*n)
part 2
现在我们打破惯性思维,换一种思考方式。回想一下,我们开始写题的时候是不是就直接被答案的单调性带到了那个思路?想一想,如果我们不采用二分这种自带log的做法呢?
考虑直接枚举最大值和最小值,这样的话就没有什么操作余地了。但是枚举最小值的过程中,我们是不是可以有一些什么操作呢?
如果从最大值向下逐个枚举最小值,那么中间的所有的边都是可选的,那么正解的做法就呼之欲出了:在第二层枚举时使用并查集维护点之间的联通性!
众所周知,并查集的时间复杂度是反阿克曼函数,可以近似看成一个小常数忽略不计,这样的话最后的时间复杂度就是
注意一旦联通就 break 掉使程序更快,另外注意也可以提前用并查集判断答案的是否可行性。
代码如下
#include<bits/stdc++.h>
#define N 505
#define M 5005
using namespace std;
int n,m;
int ansx=-1,ansy=1;
int s,t,fa[N];
int get(int x){return x==fa[x]?x:fa[x]=get(fa[x]);}
int gcd(int a,int b){return b?gcd(b,a%b):a;}
struct F{int x,y,z;}p[M];
bool cmp(F a,F b){return a.z<b.z;}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=m;i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
p[i]=(F){x,y,z};
fa[get(x)]=get(y);
}
scanf("%d%d",&s,&t);
if(get(s)!=get(t)) printf("IMPOSSIBLE"),exit(0);
sort(p+1,p+1+m,cmp);
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++) fa[j]=j;
for(int j=i;j;j--)
{
fa[get(p[j].x)]=get(p[j].y);
if(get(s)==get(t))
{
int mx=p[i].z,mn=p[j].z;
double now=mx*1.0/mn,Ans=ansx*1.0/ansy;
if(ansx==-1||now<Ans) ansx=mx,ansy=mn;
break;
}
}
}
int g=gcd(ansx,ansy);
if(ansx%ansy==0) printf("%d\n",ansx/g);
else printf("%d/%d\n",ansx/g,ansy/g);
}//O(m*m*\alpha n)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!