树是一种很常见的数据结构。现在小明面临一个问题,在一个有 n个节点的树上,节点编号分别是
1…n。小明想知道一些节点之间的最近公共祖先是那些节点。 输入格式:第一行输入一个整数 n(2≤n≤10,000),表示树上有 n个节点。 接下来的 n−1 行,每行输入俩个整数 a,b(1≤a,b≤n)代表节点 a,b 之间有一条 a 到 b 边,a 是 b 的父亲。 接下来输入一个整数 q,代表小明的 q 次提问。(1≤q≤1,000) 接下来的 q 行,每行输入俩个整数 c,d(1≤c,d≤n)代表询问 c,d 俩个节点的最近公共祖先。 输出格式:对于每次询问,输出公共祖先的节点编号,占一行。
样例输入
5
1 2
2 3
1 4
2 5
2
3 4
3 5
样例输出
1
2
这道题属于图论里的LCA(least common ancestor)问题,如我我们暴力求解的话时间复杂度就是O(V);但是如果用倍增算法的话可以把一次的查询复杂度就会降到O(log V)
首先什么是倍增算法?
倍增解法的核心是分治思想。当已知两个点在树中的深度时,先让较深的结点
向上走,直到两个结点深度一样;再二分找出离他们最近的公共祖先。我们记
一个结点的父结点为它的 20=1 倍祖先,它的父结点的父结点为它的 2 倍祖先,
以此类推。
倍增算法的具体流程第一步:
一、在树上预处理每个结点的深度和 1倍祖先,也就是每个结点的父结点。用d数组来表示每个
结点的深度,p[v][h]表示结点 v的 h倍祖先的结点编号。d数组中的元素初始为 −1。初始化完
成后,p[a][0]保存的是a的第 20=1 倍祖先结点,即它的父结点。时间复杂度 O(V)。
const int MAX_N = 100000;
const int MAX_M = 1000000;
int d[MAX_N], p[MAX_N][20];
void init() {
memset(d, -1, sizeof(d)); }
void dfs(int node) {
for (int i = f[node]; i != -1; i = e[i].next) {
if (d[e[i].v] == -1) {
d[e[i].v] = d[node] + 1;
p[e[i].v][0] = node;
dfs(e[i].v);
}
}
}
倍增算法的具体流程第二步:
二、倍增计算各个点的 ??祖先是谁,其中,1倍祖先就是父亲,2倍祖先是父亲
的父亲,以此类推。该点?? 祖先也就是该点 ??−?祖先的 ??−? 祖先。
for (int level = 1; (1 << level) <= n; level++) {
for (int i = 1; i <= n; i++) {
p[i][level] = p[p[i][level - 1]][level - 1]; // i 的第 2^j 祖先就是 i 的
//第 2^(j-1) 祖先的第 2^(j-1) 祖先
}
}
在这一步中,??????表示的是祖先的倍数。很显然,i 的 ??倍祖先就是i 的 ??−?
倍祖先的 ??−?倍祖先。时间复杂度为O(VlogV)。
倍增算法的具体流程第三步:
这是我们老师的代码,在许多地方都用了移位来优化。
下面贴我的代码
#include <iostream>
#include <string.h>
using namespace std;
const int MAX_N=1000;
const int MAX_M=10000;
int head[MAX_N];
bool visit[MAX_N];
int depth[MAX_N];//记录深度
int fa[MAX_N][21];//记录i节点的2^j倍祖先节点的编号
int ans=0;
struct edge{
int to;
int next;
}eid[MAX_M];
void insert(int u,int v){
eid[ans].to=v;
eid[ans].next=head[u];
head[u]=ans++;
}
inline int read(){
int s=0,w=1;
char ch=getchar();
while (ch<'0' || ch> '9'){
if(ch=='-')
w=-1;
ch=getchar();
}
while (ch>='0' && ch<='9'){
s=10*s+ch-'0';
ch=getchar();
}
return s*w;
}//快读,等会再介绍一下快读
void swap(int& a,int& b){
int temp=a;
a=b;
b=temp;
}
//这里用深度搜索来预处理深度和fa[i][0]的信息。这里用了visit[]数组来记录有没有搜索过。因为我们的边是无向边,
//但是这道题没有必要因为他给的信息都是小的点作为父节点的。
void dfs(int now,int dep,int fat){
if(visit[now]) return;//1
visit[now]=true;//2 这两句可要可不要
depth[now]=dep;
fa[now][0]=fat;//now节点的父亲节点是fat
for(int i=head[now];i!=-1;i=eid[i].next){
dfs(eid[i].to,dep+1,now);
}
}
int lca(int x,int y){
if(depth[x]<depth[y])
swap(x,y);//让深的节点为X
for(int i=20;i>=0;--i){
if(fa[x][i]!=0 && depth[fa[x][i]]>=depth[y]){
x=fa[x][i];
}
}//这里相当于二进制的位选择,从第20位( 2^20)一直到第0位(2^0)进行选择跳跃,【哪些位被选择了呢?就是Y与X的深度差值的二进制中的那些为1的位】最后的结果就是X与Y的深度变成了相同
if(x==y) return x;
for(int i=20;i>=0;--i){
if(fa[x][i]!=0 && fa[x][i]!=0 && fa[x][i]!=fa[y][i]){
x=fa[x][i];
y=fa[y][i];
}
}
return fa[x][0];
}
//当他们的点相同时说明刚好都到达了最近公共祖先的点,如果没有到则再进行二分跳跃。如果其中一个点跳到了根节点或者以上为0的时候,或者两个相同(意味着跳过了)则选择跳过当次迭代。
int main() {
memset(head,-1,sizeof(head));
int n=read();
for(int i=0;i<n-1;++i){
int x,y;
x=read();
y=read();
insert(x,y);
insert(y,x);
}
dfs(1,1,0);
for(int j=1;j<=20;++j){
for(int i=1;i<=n;++i){
fa[i][j]=fa[fa[i][j-1]][j-1];
}
}
int q;
q=read();
for(int i=0;i<q;++i){
int x,y;
x=read();
y=read();
printf("%d\n",lca(x,y));
}
return 0;
}
这讲一下快读和快写。
首先呢 无非就是cin 因为比较好用 简单
cin>>x;
1
然后听老师 说要用scanf() 读入更快 在省赛里面啥的很有用
scanf()
1
然后在某一篇题解里面看见 ios::sync_with_stdio(false) 可以加速(也就是关闭同步流)
cin慢是有原因的,其实默认的时候,cin与stdin总是保持同步的,也就是说这两种方法可以混用,而不必担心文件指针混乱,同时cout和stdout也一样,两者混用不会输出顺序错乱。正因为这个兼容性的特性,导致cin有许多额外的开销,如何禁用这个特性呢?只需一个语句std::ios::sync_with_stdio(false);,这样就可以取消cin于stdin的同步了。
所以在主函数里面(当然是在输出前面)加了一句
ios::sync_with_stdio(false);
1
再然后就是平时做题 容易TLE 就发现了一种高端的东西叫做快读 也就是read
什么是快读
我们知道,scanf比cin快,在读入量大的时候便可以优化时间复杂快.
快读就是一种读入很快的读入方法,可以优化时间复杂度.
快读很快,比cin与scanf都快,可以极大优化时间复杂度
为什么要用快读
1.我们知道cin和scanf不能混用,但在保留小数的时候不得不用printf,我们知道cin和printf不能混用,这时候便可以用快读read,再用printf
2.很快…….过会儿在举例中可以得到
快读为什么很快
int读是很慢的,我们需要用更快的字符的读入方法getchar进行读入
快读原理
用字符读入,再转换成数字,最后输出.
templateinline void read(t&x) {
x=0;
int f=1;
char ch=getchar();
while(!isdigit(ch)) {
if(ch=’-’) f=-1;
ch=getchar();
}
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch-48),ch=getchar();
x*=f;
}
然后献上模板
#include <bits/stdc++.h>
#define f(i,j,n) for(register int i=j;i<=n;i++)
#define openfile freopen(".in",“r”,stdin),freopen(".out",“w”,stdout);
#define closefile fclose(stdin),fclose(stdout);
using namespace std;
typedef long long ll;
templateinline void read(t&x) {
x=0;
int f=1;
char ch=getchar();
while(!isdigit(ch)) {
if(ch=’-’) f=-1;
ch=getchar();
}
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch-48),ch=getchar();
x*=f;
}
signed main() {
//openfile
//closefile
return 0;
}
作者:清风ღ
来源:优快云
原文:https://blog.youkuaiyun.com/qq_42628055/article/details/85177064
版权声明:本文为博主原创文章,转载请附上博文链接!