Day 5 LCA 最近公共祖先

版权声明:https://blog.csdn.net/huashuimu2003 https://blog.csdn.net/huashuimu2003/article/details/87277519

A. POJ 1330 Nearest Common Ancestors

题目

POJ 1330

题解

LCA倍增求法模板题。唯一恶心的地方就是输入的树不一定是以1号节点为根节点的,需要找到根节点,至于方法,我相信你会。。。。。。我一直WA的原因:lca中的判断步骤少写了一个 = =

代码

#include<algorithm>
#include<bitset>
#include<cctype>
#include<cerrno>
#include<clocale>
#include<cmath>
#include<complex>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<ctime>
#include<deque>
#include<exception>
#include<fstream>
#include<functional>
#include<limits>
#include<list>
#include<map>
#include<iomanip>
#include<ios>
#include<iosfwd>
#include<iostream>
#include<istream>
#include<ostream>
#include<queue>
#include<set>
#include<sstream>
#include<stack>
#include<stdexcept>
#include<streambuf>
#include<string>
#include<utility>
#include<vector>
#include<cwchar>
#include<cwctype>
using namespace std;
const int maxn=1e4+10;
template<typename T>inline void read(T &x)
{
	x=0;
	T f=1,ch=getchar();
	while (!isdigit(ch)) ch=getchar();
	if (ch=='-') f=-1, ch=getchar();
	while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	x*=f;
}
int ver[maxn],Next[maxn],head[maxn],len;
inline void add(int x,int y)
{
	ver[++len]=y,Next[len]=head[x],head[x]=len;
}
queue<int>q;
int d[maxn],f[maxn][21],vis[maxn],t;
inline void bfs(int root)
{
	q.push(root);
	d[root]=1;
	while (!q.empty())
	{
		int x=q.front();
		q.pop();
		for (int i=head[x];i;i=Next[i])
		{
			int y=ver[i];
			if (d[y]) continue;
			d[y]=d[x]+1;
			f[y][0]=x;
			for (int j=1;j<=t;++j)
				f[y][j]=f[f[y][j-1]][j-1];
			q.push(y);
		}
	}
}
inline int lca(int x,int y)
{
	if (d[x]>d[y]) swap(x,y);
	for (int i=t;i>=0;--i)
		if (d[f[y][i]]>=d[x]) y=f[y][i];//==就是这里!!!!!!!==
	if (x==y) return x;
	for (int i=t;i>=0;--i)
		if (f[x][i]!=f[y][i])
			x=f[x][i],y=f[y][i];
	return f[x][0];
}
int main()
{
	int c;read(c);
	while (c--)
	{
		int n;read(n);
		t=(int)(log(n)/log(2))+1;
		memset(d,0,sizeof(d));
		memset(f,0,sizeof(f));
		memset(vis,0,sizeof(vis));
		memset(head,0,sizeof(head));
		len=0;
		for (int i=1;i<n;++i)
		{
			int x,y;read(x);read(y);
			add(x,y);
			vis[y]=1;
		}
		for (int i=1;i<=n;++i)
			if (!vis[i])
			{
				bfs(i);
				break;
			}
		int x,y;read(x);read(y);
		printf("%d\n",lca(x,y));
	}
	return 0;
}

B. HDU 2586 How far away ?

题目

HDU 2586

题解

不会的,通通回去读《算法竞赛进阶指南》。

代码

#include<bits/stdc++.h>
using namespace std;
inline int read()
{
    int f=1,num=0;
    char ch=getchar();
    while (ch<'0'||ch>'9') { if (ch=='-') f=-1; ch=getchar(); }
    while (ch>='0'&&ch<='9') num=(num<<1)+(num<<3)+ch-'0', ch=getchar();
    return num*f;
}
const int SIZE=50010;
int f[SIZE][20],d[SIZE],dist[SIZE];
int ver[SIZE*2],Next[SIZE*2],edge[SIZE*2],head[SIZE];
int T,n,m,tot,t;
queue<int>q;
void add(int x,int y,int z)
{
    ver[++tot]=y,edge[tot]=z,Next[tot]=head[x],head[x]=tot;
}
void bfs()
{//预处理 
    q.push(1);
    d[1]=1;
    while (!q.empty())
    {
        int x=q.front();
        q.pop();
        for (int i=head[x];i;i=Next[i])
        {
            int y=ver[i];
            if (d[y]) continue;
            d[y]=d[x]+1;
            dist[y]=dist[x]+edge[i];
            f[y][0]=y;
            for (int j=1;j<=t;j++)
                f[y][j]=f[f[y][j-1]][j-1];
            q.push(y);
        }
    }
}
int lca(int x,int y)//回答一个询问 
{
    if (d[x]>d[y]) swap(x,y);
    for (int i=t;i>=0;i--)
        if (d[f[y][i]]>=d[x]) y=f[y][i];
    if (x==y) return x;
    for (int i=t;i>=0;i--)
        if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    return f[x][0];
}
int main()
{
    T=read();
    while (T--)
    {
        n=read(),m=read();
        t=(int)(log(n)/log(2))+1;
        //清空 
        for (int i=1;i<=n;++i)
            head[i]=d[i]=0;
        tot=0;
        //读入一棵树 
        for (int i=1;i<n;++i)
        {
            int x=read(),y=read(),z=read();
            add(x,y,z),add(y,x,z);
        }
        bfs();
        //回答问题 
        for (int i=1;i<=m;++i)
        {
            int x=read(),y=read();
            printf("%d\n",dist[x]+dist[y]-2*dist[lca(x,y)]);
        }
    }
    return 0;
}

C. BZOJ 1787: [Ahoi2008]Meet 紧急集合

题目

BZOJ 1787

题解

求三个结点到一个结点距离之和最小的结点以及距离和
求出两两lca,其中有两个相同,答案则为另一个。
摘自hzwer

代码

来自鑫神的代码。实在不好意思,鑫神不太喜欢写博客,所以没办法让大家去欣赏一下鑫神的博客,在这里表示最诚挚的歉意。

#include<bits/stdc++.h>
#define N 500001
using namespace std;
int f[N][20],d[N],dist[N],lin[N*2],ans;
inline int read() {
    int s = 0, w = 1;
    char ch = getchar();
    while (!isdigit(ch)) { if (ch == '-') w = -1; ch = getchar(); }
    while (isdigit(ch)) { s = (s << 1) + (s << 3) + (ch ^ 48); ch = getchar(); }
    return s * w;
}
struct gg
{
	int y,v,next;
}a[N<<1];
int T,n,m,tot,t;
queue<int> q;
void add(int x,int y)
{
	a[++tot].y=y;
	a[tot].next=lin[x];
	lin[x]=tot;
}

void bfs()
{
	q.push(1);d[1]=1;
	while(q.size())
	{
		int x=q.front();q.pop();
		for(int i=lin[x];i;i=a[i].next)
		{
			int y=a[i].y;
			if(d[y]) continue;
			d[y]=d[x]+1;
			f[y][0]=x;
			for(int j=1;j<=t;j++)
				f[y][j]=f[f[y][j-1]][j-1];
			q.push(y);
		}
	}
}
int lca(int x,int y)
{
	ans=0;
	if(d[x]>d[y]) swap(x,y);
	for(int i=t;i>=0;i--)
		if(d[f[y][i]]>=d[x]) y=f[y][i];
	if(x==y) return x;
	for(int i=t;i>=0;i--)
		if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
	return ans=f[x][0];
}
int dis(int x,int y)
{
	int t=lca(x,y);
	return d[x]+d[y]-2*d[t];
}
int main()
{
	queue<int> q;
	n=read();m=read();
	t=(int)(log(n)/log(2))+1;
	for(int i=1;i<=n;i++) lin[i]=d[i]=0;
	tot=0;
	for(int i=1;i<n;i++)
	{
		int x,y;
		x=read();y=read();
		add(x,y);add(y,x);
	}
	bfs();
	for(int i=1;i<=m;i++)
	{
		int x,y,z;
		x=read();y=read();z=read();
		int xx,yy,zz;
		int p1=lca(x,y),p2=lca(x,z),p3=lca(y,z),t;
		if(p1==p2) t=p3;
		else if(p2==p3) t=p1;
		else t=p2;
		int ans=dis(x,t)+dis(y,t)+dis(z,t);
		cout<<t<<' '<<ans<<endl;
	}
	return 0;
}

或者将三个lca分别计算取最优。
下面这份代码跑的较快。大概 3 s e c 3sec 左右。上份代码大概是 10 s e c 10sec 左右。

#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+10;
const int inf=0x7fffffff;
template<typename T>inline void read(T &x)
{
	x=0;
	T f=1,ch=getchar();
	while (!isdigit(ch)) ch=getchar();
	if (ch=='-') f=-1, ch=getchar();
	while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
	x*=f;
}
int ver[maxn<<1],Next[maxn<<1],head[maxn],len;
inline void add(int x,int y)
{
	ver[++len]=y,Next[len]=head[x],head[x]=len;
}
queue<int>q;
int d[maxn],f[maxn][21],vis[maxn],t;
inline void bfs(int root)
{
	q.push(root);
	d[root]=1;
	while (!q.empty())
	{
		int x=q.front();
		q.pop();
		for (int i=head[x];i;i=Next[i])
		{
			int y=ver[i];
			if (d[y]) continue;
			d[y]=d[x]+1;
			f[y][0]=x;
			for (int j=1;j<=t;++j)
				f[y][j]=f[f[y][j-1]][j-1];
			q.push(y);
		}
	}
}
inline int lca(int x,int y)
{
	if (d[x]>d[y]) swap(x,y);
	for (int i=t;i>=0;--i)
		if (d[f[y][i]]>=d[x]) y=f[y][i];
	if (x==y) return x;
	for (int i=t;i>=0;--i)
		if (f[x][i]!=f[y][i])
			x=f[x][i],y=f[y][i];
	return f[x][0];
}
int main()
{
	int n,m,id,ans;read(n);read(m);
	t=log2(n*1.0);
	memset(d,0,sizeof(d));
	memset(f,0,sizeof(f));
	memset(vis,0,sizeof(vis));
	memset(head,0,sizeof(head));
	len=0;
	for (int i=1;i<n;++i)
	{
		int x,y;read(x);read(y);
		add(x,y);add(y,x);
	}
	bfs(1);
	for (int i=1;i<=m;++i)
	{
		int x,y,z;read(x);read(y);read(z);
		int l1=lca(x,y),l2=lca(x,z),l3=lca(y,z),ans=inf,tmp,id;
		int q1=lca(l1,z),q2=lca(l2,y),q3=lca(l3,x);
		tmp=d[x]+d[y]-d[l1]+d[z]-(d[q1]<<1);
		if (tmp<ans)
			ans=tmp,id=l1;
		tmp=d[x]+d[z]-d[l2]+d[y]-(d[q2]<<1);
		if (tmp<ans)
			ans=tmp,id=l2;
		tmp=d[y]+d[z]-d[l3]+d[x]-(d[q3]<<1);
		if (tmp<ans)
			ans=tmp,id=l3;
		printf("%d %d\n",id,ans);
	}
	return 0;
}

D. UVA11354 Bond

题目

LUOGU UVA 11354 最小生成树+倍增求LCA
双倍经验:NOIP 2013 货车运输 最大生成树+倍增求LCA

题解

这个题啊!真恶心啊!
1.首先,我们将读入数据用最小生成树直接建立一颗树。
2.然后,再跑一遍倍增求LCA,既可以了。
细节等空闲时在说清楚。其实代码很清楚的

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=6e5+10;
const int inf=0x3f3f3f3f;
template<typename T>inline void read(T &x)
{
    x=0;
    T f=1,ch=getchar();
    while (!isdigit(ch) && ch^'-') ch=getchar();
    if (ch=='-') f=-1, ch=getchar();
    while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
    x*=f;
}
struct Edge
{
    int x,y,z,next;
}G[maxn<<1],A[maxn<<1];//G[]是最大生成树的图
int n,m,head[maxn],len;
inline void add(int x,int y,int z)
{
    G[++len].y=y,G[len].z=z,G[len].next=head[x],head[x]=len;
}
int fa[maxn];
inline int get(int x)
{
    if (x==fa[x]) return x;
    return fa[x]=get(fa[x]);
}
inline bool cmp(Edge a,Edge b)
{
//	return a.z>b.z;
    return a.z<b.z;
}
inline void Kruskal()
{
    sort(A+1,A+m+1,cmp);
    for (int i=1;i<=n;++i)
        fa[i]=i;
    for (int i=1;i<=m;++i)
    {
        int x=get(A[i].x),y=get(A[i].y);
        if (x!=y)
        {
            fa[y]=x;
            add(A[i].x,A[i].y,A[i].z);
            add(A[i].y,A[i].x,A[i].z);
        }
    }
}
int d[maxn],f[maxn][21],w[maxn][21];//fa[]表示并查集中的父节点,f[][]表示树上的父节点,w[][]表示最大载重
inline void dfs(int x)
{
    for (int i=1;i<=20;++i)//LCA初始化
    {
        f[x][i]=f[f[x][i-1]][i-1];
//		w[x][i]=min(w[x][i-1],w[f[x][i-1]][i-1]);
        w[x][i]=max(w[x][i-1],w[f[x][i-1]][i-1]);
    }
    for (int i=head[x];i;i=G[i].next)
    {
        int y=G[i].y;
        if (d[y]) continue;
        d[y]=d[x]+1;//计算深度
        f[y][0]=x;//储存父节点
        w[y][0]=G[i].z;//储存到父节点的权值
        dfs(y);
    }
}
inline int lca(int x, int y)
{
    if (get(x)!=get(y)) return -1;//不连通,输出-1
//	int ans=inf;
    int ans=0;
    if (d[x]>d[y]) swap(x,y);//保证y节点更深
    for (int i=20;i>=0;--i)//将y节点上提到于x节点相同深度
        if (d[f[y][i]]>=d[x])
        {
//			ans=min(ans,w[y][i]);//更新最大载重(最小边权)
            ans=max(ans,w[y][i]);
            y=f[y][i];//修改y位置
        }
    if (x==y) return ans;//如果位置已经相等,直接返回答案
    for (int i=20;i>=0;--i)//寻找公共祖先
        if (f[x][i]!=f[y][i])
        {
//			ans=min(ans,min(w[x][i], w[y][i]));//更新最大载重(最小边权)
            ans=max(ans,max(w[x][i],w[y][i]));
            x=f[x][i],y=f[y][i];//修改x,y位置
        }
//  ans=min(ans,min(w[x][0],w[y][0]));//更新此时x,y到公共祖先最大载重,f[x][0], f[y][0]即为公共祖先
    ans=max(ans,max(w[x][0],w[y][0]));
    return ans;
}
int main()
{
	int flag=0;
    while (scanf("%d %d",&n,&m)!=EOF)
    {
    	if (flag) printf("\n");
    	else flag=1;
        memset(f,0,sizeof(f));
        memset(w,0,sizeof(w));
        memset(d,0,sizeof(d));
        memset(head,0,sizeof(head));
        len=0;
        for (int i=1;i<=m;++i)
            read(A[i].x),read(A[i].y),read(A[i].z);
        Kruskal();
        for (int i=1;i<=n;++i)//dfs收集信息
        	if (!d[i])
            {
            	d[i]=1;
            	dfs(i);
            	f[i][0]=i;
//				w[i][0]=inf;
                w[i][0]=-inf;
        	}
        int q;
        read(q);
        while (q--)
        {
            int x,y;
            read(x);read(y);
            printf("%d\n",lca(x,y));
        }
    }
    return 0;
}

E. BZOJ 1977 【模板】严格次小生成树[BJWC2010]

题目

luogu 4180
BZOJ 1977

代码

之前写过这个题,今天再回顾一遍吧,但是博客不想再赘述了,就看这个吧。
严格次小生成树

F. BZOJ 2144: 跳跳棋

题目

BZOJ 2144
LUOGU 1852

题解

真的不会,这个是抄黄学长的代码。 QWQ

代码

/**************************************************************
    Problem: 2144
    User: hsm
    Language: C++
    Result: Accepted
    Time:20 ms
    Memory:1292 kb
****************************************************************/

#include<bits/stdc++.h>
#define inf 1000000000
using namespace std;
template<typename T>inline void read(T &x)
{
    x=0;
    T f=1,ch=getchar();
    while (!isdigit(ch) && ch^'-') ch=getchar();
    if (ch=='-') f=-1, ch=getchar();
    while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
    x*=f;
}
int tmp,ans;
int a[5],b[5];
struct data
{
    int a[5];
};
data cal(int *a,int k)//得到a状态向上走k次的状态
{
    data ans;
    int t1=a[2]-a[1],t2=a[3]-a[2];
    for (int i=1;i<=3;++i)
        ans.a[i]=a[i];
    if (t1==t2) return ans;
    if (t1<t2)
    {
        int t=min(k,(t2-1)/t1);
        k-=t,tmp+=t;//顺便记录深度
        ans.a[2]+=t*t1,ans.a[1]+=t*t1;
    }
    else
    {
        int t=min(k,(t1-1)/t2);
        k-=t,tmp+=t;
        ans.a[2]-=t*t2,ans.a[3]-=t*t2;
    }
    if (k) return cal(ans.a,k);//辗转相除
    else return ans;
}
bool operator != (data a,data b)
{
    for (int i=1;i<=3;++i)
        if (a.a[i]!=b.a[i])
            return 1;
    return 0;
}
int main()
{
    for (int i=1;i<=3;++i)
        read(a[i]);
    for (int i=1;i<=3;++i)
        read(b[i]);
    sort(a+1,a+4);
    sort(b+1,b+4);
    data t1=cal(a,inf);int d1=tmp;tmp=0;
    data t2=cal(b,inf);int d2=tmp;tmp=0;//t1,t2分别为a,b的根,d1,d2为深度
    if (t1!=t2)
    {
        puts("NO");
        return 0;
    }
    if (d1>d2)
    {
        swap(d1,d2);
        for (int i=1;i<=3;++i)
            swap(a[i],b[i]);
    }
    ans=d2-d1;
    t1=cal(b,ans);
    for (int i=1;i<=3;++i)
        b[i]=t1.a[i]; //较深的向上调整
    int l=0,r=d1;
    while (l<=r)//二分
    {
        int mid=(l+r)>>1;
        if (cal(a,mid)!=cal(b,mid)) l=mid+1;
        else r=mid-1;
    }
    puts("YES");
    printf("%d",ans+2*l);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/huashuimu2003/article/details/87277519
今日推荐