洛谷P1967 货车运输

首先说这道题的思路:存在多条路径时求路径最大权的最小值问题,可以利用生成树的性质重建给出的图,将多路径转化为一定最优的单一路径,再在树上求LCA时统计权值来求出“路径最大权的最小值”。

写这份代码的时候复习了几个重要的知识点:

并查集

在写生成树时,经常会用到并查集。

ll find(ll x)
{
	if(b[x]==x) return x;
	else return b[x]=find(b[x]);
} 

并查集的代码非常简单,需要注意的是,在合并时,语句为:

f[find(x)]=find(y);

而不是:

f[x]=find(y);

下面的写法会漏合并x所在的集合的其他点 (我在这里卡了是真的丢人)

LCA

这道题中的LCA与一般的LCA大同小异,只是在求 2 i 2^i 2i级父节点时一起将到 2 i 2^i 2i级父节点的“路径最大权的最小值”一起求出来,在倍增求LCA时也加入统计“路径最大权的最小值”的代码即可。同时,由于这道题可能给出森林,因此需要将每个点都作为根来dfs,同时加入特判来判定某个点是否已经属于某棵已知的树。

void dfs(ll k,ll fa)
{
	if(v[k]) return;//特判
	v[k]=1;
	d[k]=d[fa]+1;
	f[k][0]=fa;
	for(int i=1;(1<<i)<d[k];i++)
	{
		f[k][i]=f[f[k][i-1]][i-1];
		w[k][i]=min(w[k][i-1],w[f[k][i-1]][i-1]);//求“路径最大权的最小值”,与求f[k][i]同理
	}
	for(int i=head[k];i;i=l[i].n)
	{
		if(l[i].r==fa) continue;
		w[l[i].r][0]=l[i].w;
		dfs(l[i].r,k);
	}
} 
ll lca(ll u,ll p)
{
	ll ans=maxn;
	if(find(u)!=find(p)) return -1;
	if(d[u]<d[p]) swap(u,p);
	for(int i=19;i>=0;i--)
		if(f[u][i]&&d[f[u][i]]>=d[p]) 
		{
			ans=min(ans,w[u][i]);//统计“路径最大权的最小值”
			u=f[u][i];
		}
	if(u==p) return ans;
	for(int i=19;i>=0;i--)
	{
		if(f[u][i]!=f[p][i])
		{
			ans=min(ans,min(w[u][i],w[p][i]));//统计“路径最大权的最小值”
			u=f[u][i];
			p=f[p][i];
		}
	}
	ans=min(ans,min(w[u][0],w[p][0]));//统计“路径最大权的最小值”
	return ans;
}

其他重要操作还有前向星生成树,不过这些较为简单,就不再赘述,代码见下方即可。

AC代码:

#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int maxn=1e5+5;
ll n,m,x,y,z,q;
ll b[maxn];//并查集
ll head[maxn],ecnt=1;//前向星
ll v[maxn],f[maxn][20],d[maxn];//lca 
ll w[maxn][20];//lca中到父节点的最小边权 
struct edge{
	int l,r,w;
}e[maxn]; 
struct list{
	int r,n,w;
}l[maxn];
bool cmp(edge a,edge b) {return a.w>b.w;}
ll find(ll x)
{
	if(b[x]==x) return x;
	else return b[x]=find(b[x]);
} 
void swap(int &a,int &b)
{
	int c=a;
	a=b;
	b=c;
} 
void add(ll a,ll b,ll c)
{
	l[ecnt].r=b;
	l[ecnt].w=c;
	l[ecnt].n=head[a];
	head[a]=ecnt++;
}
void scs()
{
	for(int i=1;i<=n;i++) b[i]=i;
	for(int i=1;i<=m;i++)
	{
		if(find(e[i].l)!=find(e[i].r))
		{
			b[find(e[i].l)]=find(e[i].r);
			add(e[i].l,e[i].r,e[i].w);
			add(e[i].r,e[i].l,e[i].w);
		}
	}
}
void dfs(ll k,ll fa)
{
	if(v[k]) return;
	v[k]=1;
	d[k]=d[fa]+1;
	f[k][0]=fa;
	for(int i=1;(1<<i)<d[k];i++)
	{
		f[k][i]=f[f[k][i-1]][i-1];
		w[k][i]=min(w[k][i-1],w[f[k][i-1]][i-1]);
	}
	for(int i=head[k];i;i=l[i].n)
	{
		if(l[i].r==fa) continue;
		w[l[i].r][0]=l[i].w;
		dfs(l[i].r,k);
	}
} 
ll lca(ll u,ll p)
{
	ll ans=maxn;
	if(find(u)!=find(p)) return -1;
	if(d[u]<d[p]) swap(u,p);
	for(int i=19;i>=0;i--)
		if(f[u][i]&&d[f[u][i]]>=d[p]) 
		{
			ans=min(ans,w[u][i]);
			u=f[u][i];
		}
	if(u==p) return ans;
	for(int i=19;i>=0;i--)
	{
		if(f[u][i]!=f[p][i])
		{
			ans=min(ans,min(w[u][i],w[p][i]));
			u=f[u][i];
			p=f[p][i];
		}
	}
	ans=min(ans,min(w[u][0],w[p][0]));
	return ans;
}
/*void out()
{
	for(int i=1;i<=n;i++)
	{
		for(int j=head[i];j;j=l[j].n)
			printf("%d %d\n",l[j].r,l[j].w);
	}
}*/
int main()
{
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=m;i++) scanf("%d%d%d",&e[i].l,&e[i].r,&e[i].w);
	sort(e+1,e+m+1,cmp);
/*	printf("\n");
	for(int i=1;i<=m;i++) printf("%d %d %d\n",e[i].l,e[i].r,e[i].w);*/
	scs();
/*	printf("\n");
	out();
	printf("\n");*/
	for(int i=1;i<=n;i++) dfs(i,0);
	scanf("%lld",&q);
	for(int i=1;i<=q;i++)
	{
		scanf("%lld%lld",&x,&y);
		printf("%lld\n",lca(x,y));
	}
	return 0;
}

以及,善用注释!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值