倍增/ST表
树上倍增
利用二进制的思想,二进制的数可以表示出所有十进制的数,令f[i][j]表示i的第 个祖先,i = f[i][j]表示i跳到第j个祖先上去,假如我们要求i的第k个祖先,我们不需要一步一步的往上走,利用二进制的思想,我们可以一次条多步的往上走,例如要求i的第5个祖先,5的二进制形式是101,即
那么我们先跳到i的第一个祖先t,然后t跳到t的第4个祖先,也就求到了i的第五个祖先。
树上倍增的应用:
1) 求某个节点的第k个祖先
2) 求a,b的最近公共祖先LCA
......
如何求f[i][j]呢,对树根节点跑一边dfs即可
void dfs(int so,int fa)
{
f[so][0] = fa;
//h[so] = h[fa] + 1;
for(int i = 1; i <= 19; i++) {
f[so][i] = f[f[so][i-1]][i-1];//倍增,so的第2^i个祖先也就是so的第2^(i-1)个祖先的第2^(i-1)的祖先,可以画图理解。
}
for(int i = head[so]; ~i; i = edge[i].next){
int to = edge[i].v;
if(to == fa) continue;
len[to] = len[so]+1;
dfs(to,so);
}
}1) 求某个节点的第k个祖先
int Kth(int u,int k)
{
int b = 0;
while(k){
if(k & 1) u = f[u][i];
k >>= 1;
b++;
}
return u;
}2) 求x,y的最近公共祖先LCA
求法:首先我们要保证x,y在同一层,假设x在更深的一层,那么我们要让他上升到和y一样的高度,利用二进制的思想跳上去,我们优先往远的地方跳,如果大于了h[y],那么不跳,如果小于等于h[y],就要跳,为什么呢,因为当前祖先的深度都比y深了,而我们是从高处往低处枚举的,之后的祖先也一定比y更深,所有我们先跳上去,去判断当前祖先的祖先与y的深度。到达同一层之后,如果x==y那么直接返回x,否则的后继续寻找最近的公共祖先。看下面代码:
int lca(int x,int y)
{
//h[x]表示节点x的深度。
if(h[y] > h[x]) swap(x,y);// 保证x更深
for(int i = 19; i >= 0; i--){
if(h[f[x][i]] >= h[y]){
x = f[x][i];
}
}
if(x == y) return x;
for(int i = 19; i >= 0; i--){
if(f[x][i] != f[y][i]){
x = f[x][i];
y = f[y][i];
}
}
return f[x][0];//x的第一个祖先最近。
}
LCA模板题
https://www.luogu.com.cn/problem/P3379
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <stack>
#include <queue>
#include <string.h>
#include <cmath>
#include <bitset>
#define ll long long
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;
using namespace std;
//void print(char a[])
//{
// printf("%d %c\n",strlen(a),a[2]);
//}
struct edeg{
int v,next;
}edge[2000005];
int head[1000005],f[1000005][20],h[1000005],cnt;
void add(int u,int v)
{
edge[cnt].next = head[u];
edge[cnt].v = v;
head[u] = cnt++;
}
void dfs(int s,int fa)
{
f[s][0] = fa;
for(int i = 1; i <= 19; i++){
f[s][i] = f[f[s][i - 1]][i - 1];
}
h[s] = h[fa] + 1;
for(int i = head[s]; ~i; i = edge[i].next){
int to = edge[i].v;
if(to == fa) continue;
dfs(to,s);
}
}
int lca(int x,int y)
{
if(h[y] > h[x]) swap(x,y);//保证x更深。
for(int i = 19; i >= 0; i--){
if(h[f[x][i]] >= h[y]) x = f[x][i];
}
if(x == y) return x;
for(int i = 19; i >= 0; i--){
if(f[x][i] != f[y][i]){
x = f[x][i];
y = f[y][i];
}
}
return f[y][0];
}
int main()
{
int n,q,m,s;
cnt = 0;
memset(head,-1,sizeof(head));
scanf("%d%d%d",&n,&m,&s);
for(int i = 1; i < n; i++){
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
dfs(s,0);
for(int i = 1; i <= m; i++){
int x,y;
scanf("%d%d",&x,&y);
printf("%d\n",lca(x,y));
}
return 0;
}https://ac.nowcoder.com/acm/contest/7009/E
定义数组len[i]为节点i距离根节点的距离。
树中任意两点间的距离:len[a] + len[b] - 2*len[lca(a,b)].
该题保证len[a] < t 并且 len[lca(b,c)] + len[b] < t即牛牛不被挨揍。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <stack>
#include <queue>
#include <string.h>
#include <cmath>
#include <bitset>
#define ll long long
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;
using namespace std;
//void print(char a[])
//{
// printf("%d %c\n",strlen(a),a[2]);
//}
struct edeg{
int v,next;
}edge[2000005];
int head[500005],f[500005][20],len[500005],h[500005],cnt,s,ans = 0;
void add(int u,int v)
{
edge[cnt].next = head[u];
edge[cnt].v = v;
head[u] = cnt++;
}
void dfs(int so,int fa)
{
f[so][0] = fa;
h[so] = h[fa] + 1;
for(int i = 1; i <= 19; i++) {
f[so][i] = f[f[so][i-1]][i-1];
}
for(int i = head[so]; ~i; i = edge[i].next){
int to = edge[i].v;
if(to == fa) continue;
len[to] = len[so]+1;
dfs(to,so);
}
}
int lca(int x,int y)
{
if(h[y] > h[x]) swap(x,y);// 保证x更深
for(int i = 19; i >= 0; i--){
if(h[f[x][i]] >= h[y]){
x = f[x][i];
}
}
if(x == y) return x;
for(int i = 19; i >= 0; i--){
if(f[x][i] != f[y][i]){
x = f[x][i];
y = f[y][i];
}
}
return f[x][0];
}
//ll qpow(ll a,ll b)
//{
// ll res = 1;
// while(b){
// if(b & 1) res = res * a % mod;
// b >>= 1;
// a = a * a % mod;
// }
// return res;
//}
ll exgcd(int a,int b,ll &x,ll &y){
if(b == 0){
x = 1;
y = 0;
return a;
}
int t = exgcd(b,a%b,y,x);
y = y - a/b*x;
return t;
}
ll inv(int a,int mod)
{
ll x,y;
int t = exgcd(a,mod,x,y);
return (x % mod + mod) % mod;
}
int main()
{
int n,q,m;
cnt = 0;
memset(head,-1,sizeof(head));
scanf("%d",&n);
for(int i = 1; i < n; i++){
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
scanf("%d",&q);
int a,b,c,t;
dfs(1,0);
for(int i = 1; i <= q; i++){
scanf("%d%d%d%d",&a,&b,&c,&t);
int zx = lca(b,c);//t1是b,c的最近公共祖先
int l;
l = len[b] + len[c] - 2 * len[zx] + len[b];
if(len[a] < t && l < t) ans++;
}
ll q1 = inv(1ll*q,1ll*mod);
ll ans1 = (1ll*ans % mod * q1 % mod);
printf("%lld", ans1 % mod);
return 0;
}城市网络:倍增
https://ac.nowcoder.com/acm/problem/13331
思路:分析题目所给的信息,题目说每经过一个城市,如果这个城市的珠宝比当前手中的珠宝价值更高,则购入,问购买次数,很明显买完的珠宝从最后一次购买到第一次购买的价值是递减的,也就是说我从一个城市往上走,要进行购买珠宝的城市的价值是递增的。
所有我们只找比当前所在城市珠宝价值大的祖先节点,即h[i][j]表示i城市的第2^j个祖先且价值比i城市大。
再看城市u->v,为什么我们查找的时候只要找到两个城市在同一层即可呢,因为从u->v一定会经过他们的最近公共祖先,而他们的最近公共祖先又一定比v->最近公共祖先里面的所有祖先价值都高,我们不会去购买,所以跳到同一层即可。
https://ac.nowcoder.com/discuss/395376?type=101&order=0&pos=1&page=2&channel=1009&source_id=discuss_tag
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <stack>
#include <queue>
#include <string.h>
#include <cmath>
#include <bitset>
#define ll long long
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;
using namespace std;
//void print(char a[])
//{
// printf("%d %c\n",strlen(a),a[2]);
//}
int a[500005],en[500005],f[500005][20],h[500005],cnt;
vector<int>edge[500005];
void dfs(int s,int fa)
{
int x = fa;
//找到第一个比a[s]大的祖先节点
for(int i = 19; i >= 0; i--)
if(f[x][i] && a[f[x][i]] <= a[s]) x = f[x][i];
if(a[x] > a[s]) f[s][0] = x;
else f[s][0] = f[x][0];
for(int i = 1; i <= 19; i++) f[s][i] = f[f[s][i-1]][i-1];
h[s] = h[fa] + 1;
int len = edge[s].size();
for(int i = 0;i < len; i++){
int to = edge[s][i];
if(to == fa) continue;
dfs(to,s);
}
}
int main()
{
int n,q;
cnt = 0;
scanf("%d%d",&n,&q);
for(int i = 1; i <= n; i++) scanf("%d",&a[i]);
for(int i = 1; i < n; i++){
int x,y;
scanf("%d%d",&x,&y);
edge[x].push_back(y);
edge[y].push_back(x);
}
for(int i = n+1; i <= n+q; i++){
int x,y,val;
scanf("%d%d%d",&x,&y,&val);
edge[i].push_back(x);
edge[x].push_back(i);
a[i] = val;
en[i - n] = y;
}
dfs(1,0);
int ans = 0;
for(int i = 1; i <= q; i++){
ans = 0;
int x = i + n;
int v = en[i];
for(int j = 19; j >= 0; j--){
if(h[f[x][j]] >= h[v]){
ans += (1 << j);
x = f[x][j];
}
}
printf("%d\n",ans);
}
return 0;
}Borrow Classroom:LCA
https://ac.nowcoder.com/acm/problem/13813
lca模板题:判断lca(a,c)是否为一,为一说明在len[a] < len[c]+len[b,c],不为一则可以相等。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <stack>
#include <queue>
#include <string.h>
#include <cmath>
#include <bitset>
//#include <set>
#define ll long long
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;
using namespace std;
vector<int>a[100001];
int f[100001][20],len[100001],h[100001];
void dfs(int s,int fa)
{
f[s][0] = fa;
h[s] = h[fa] + 1;
for(int i = 1; i <= 17; i++) f[s][i] = f[f[s][i-1]][i-1];
int len1 = a[s].size();
for(int i = 0; i < len1; i++){
int v = a[s][i];
if(v == fa) continue;
len[v] = len[s] + 1;
dfs(v,s);
}
}
int lca(int x,int y)
{
if(len[y] > len[x]) swap(x,y);
for(int i = 17; i >= 0; i--){
if(len[f[x][i]] >= len[y]) x = f[x][i];
}
if(x == y) return x;
for(int i = 17; i >= 0; i--){
if(f[x][i] != f[y][i]){
x = f[x][i];
y = f[y][i];
}
}
return f[x][0];
}
int main()
{
//ios::sync_with_stdio(false);
int t,n,q,x,y;
cin >> t;
while(t--){
cin >> n >> q;
for(int i = 1; i <= n; i++){
a[i].clear();
}
for(int i = 1; i < n; i++){
cin >> x >> y;
a[x].push_back(y);
a[y].push_back(x);
}
len[0] = 0;
dfs(1,0);
int A,B,C,t1,t2;
while(q--){
t1 = t2 = 0;
cin >> A >> B >> C;
int zx = lca(B,C);
t1 += len[B] + len[C] - 2*len[zx] + len[C];
t2 = len[A];
if(t2 < t1) cout << "YES\n";
else if(t2 == t1 && lca(A,C) != 1) cout << "YES\n";
else cout << "NO\n";
}
}
return 0;
}
查看21道真题和解析