P2502 [HAOI2006]旅行

written on 2022-05-25

这篇题解的思考历程分为两部分。


part 1

读完题后,我们容易知道答案具有单调性,因此考虑二分答案。

对于这样两个端点均不固定的题目,我们通常采用的方式是:固定一段,二分另一端。那么对于这题而言,我们可以考虑枚举最大值,二分最小值。至于判定,用一个 dfs 或是略有修改的 SPFA(伪)均可以做到 O(n)

这种方法的总时间复杂度为 O(mlogm×(n+m))实测不可过,需要优化。

这里有三处优化:

  1. 对于不可行的情况单独计算,这样就避免了跑满的情况,防止时间爆炸。

  2. 可以考虑二分的细节,在枚举最小值时,我们不必逐个数字枚举,可以先将所有的边按边权从小到大排序,然后直接枚举边的下标。

  3. 最外层的最大值枚举是有序的,因此二分下界也是有序的,以此为据进行优化。

这样开了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的做法呢?

考虑直接枚举最大值和最小值,这样的话就没有什么操作余地了。但是枚举最小值的过程中,我们是不是可以有一些什么操作呢?

如果从最大值向下逐个枚举最小值,那么中间的所有的边都是可选的,那么正解的做法就呼之欲出了:在第二层枚举时使用并查集维护点之间的联通性!

众所周知,并查集的时间复杂度是反阿克曼函数,可以近似看成一个小常数忽略不计,这样的话最后的时间复杂度就是 O(m2α(n)),不开O2就能过。

注意一旦联通就 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)
posted @   Freshair_qprt  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示