luoguP3379板子题
代码:
现写的一份,是单纯的LCA
#include<iostream>
using namespace std;
const int N = 500005;
int en = 0,head[N] = {0};
struct Edge{
int to,next;
int val;
}edge[N*2];
int n,m,q,a,b,c,rt;
int fa[N][21] = {0},depth[N] = {0},w[N] = {0};
void add(int u,int v){
edge[++en].to = v;
edge[en].next = head[u];
head[u] = en;
}
void dfs(int u,int father){
depth[u] = depth[father]+1;
fa[u][0] = father;
for(int i=1;(1<<i)<=depth[u];i++){
fa[u][i] = fa[fa[u][i-1]][i-1];
}
for(int p=head[u]; p ;p=edge[p].next){
int v = edge[p].to;
if(v!=father) {
w[v] = w[u]+edge[p].val ;
dfs(v,u);
}
}
}
int lca(int u,int v){
if(depth[u]<depth[v]) swap(u,v);
for(int i=20;i>=0;i--){
if(depth[fa[u][i]]>=depth[v])
u = fa[u][i];
}
if(u==v) return u;
for(int i=20;i>=0;i--){
if(fa[u][i]!=fa[v][i]){
u = fa[u][i];
v = fa[v][i];
}
}
return fa[u][0];
}
void query(int u,int v){
int x = lca(u,v);
int ans = w[u]+w[v]-2*w[x];
// printf("%d\n",ans);
printf("%d\n",x);
}
int main(){
scanf("%d%d%d",&n,&q,&rt);
for(int i=1;i<n;i++){
scanf("%d%d",&a,&b);
add(a,b);
add(b,a);
}
dfs(rt,0);
for(int i=1;i<=q;i++){
scanf("%d%d",&a,&b);
query(a,b);
}
return 0;
}
这份疑似是树链剖分………………无语住了就是说,我已经忘记什么是top了…… 是每条链的顶端结点
#include<bits/stdc++.h>
using namespace std;
const int N = 500005;
int en,n,m,s,head[N],depth[N],father[N],son[N],f[N][21],top[N],size[N];
struct Edge{
int to,next;
}edge[N<<1];
inline void read(int &x){
char ch;
while((ch = getchar())<'0' || ch>'9') ;
for(x=0;ch<='9'&&ch>='0';ch = getchar())
x = x*10+ch-'0';
}
inline void add(int x,int y){
edge[en].to = y;
edge[en].next = head[x];
head[x] = en++;
}
void init(){
read(n);read(m);read(s);
memset(head,-1,sizeof(head));
for(int i=1,x,y;i<n;i++){
read(x);read(y);
add(x,y);
add(y,x);
}
}
void dfs1(int u,int f){
depth[u] = depth[f]+1;
size[u] = 1;
father[u] = f;
for(int p = head[u],v;~p;p = edge[p].next ){
v = edge[p].to ;
if(v==f) continue;
dfs1(v,u);
size[u] += size[v];
if(size[v]>size[son[u]])
son[u] = v;
}
}
void dfs2(int u){
if(son[u]) {
top[son[u]] = top[u];
dfs2(son[u]);
}
for(int v,p = head[u];~p;p = edge[p].next ){
v = edge[p].to ;
if(top[v]) continue;
top[v] = v;
dfs2(v);
}
}
int lca(int x,int y){
while (top[x]!=top[y]) {
if(depth[top[x]]<depth[top[y]]) swap(x,y);
x = father[top[x]];
}
return depth[x]<depth[y]?x:y;
}
void solve(){
for(int i=0,x,y;i<m;i++){
read(x);read(y);
printf("%d\n",lca(x,y));
}
}
int main(){
init();
dfs1(s,0);
top[s] = s;
dfs2(s);
solve();
}
树上最短路
每条边有权值的,问两点间最短路
#include<iostream>
using namespace std;
const int N = 100005;
int en = 0,head[N] = {0};
struct Edge{
int to,next;
int val;
}edge[N*2];
int n,m,q,a,b,c;
int fa[N][21] = {0},depth[N] = {0},w[N] = {0};
void add(int u,int v,int _val){
edge[++en].to = v;
edge[en].next = head[u];
head[u] = en;
edge[en].val = _val;
}
void dfs(int u,int father){
depth[u] = depth[father]+1;
fa[u][0] = father;
for(int i=1;(1<<i)<=depth[u];i++){
fa[u][i] = fa[fa[u][i-1]][i-1];
}
for(int p=head[u]; p ;p=edge[p].next){
int v = edge[p].to;
if(v!=father) {
w[v] = w[u]+edge[p].val ;
dfs(v,u);
}
}
}
int lca(int u,int v){
if(depth[u]<depth[v]) swap(u,v);
for(int i=20;i>=0;i--){
if(depth[fa[u][i]]>=depth[v])//要加等号……
u = fa[u][i];
}
if(u==v) return u;
for(int i=20;i>=0;i--){
if(fa[u][i]!=fa[v][i]){
u = fa[u][i];
v = fa[v][i];
}
}
return fa[u][0];
}
void query(int u,int v){
int x = lca(u,v);
int ans = w[u]+w[v]-2*w[x];
printf("%d\n",ans);
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
add(b,a,c);
}
dfs(1,0);
scanf("%d",&q);
for(int i=1;i<=q;i++){
scanf("%d%d",&a,&b);
query(a,b);
}
return 0;
}
tips:
- 记得写
lca
的时候开fa[N][21]
,因为20
的话会超出范围…… - 记得写
lca
的时候,第一次要挪到depth
相等的时候要取>=
,否则根本等不了的,然后导致lca
出错
三点最短路
#include<iostream>
using namespace std;
const int N = 50005;
int fa[N][18] = {0};
int depth[N] = {0},val[N] = {0},head[N] = {0};
int en,n,q,a,b,c;
struct Edge{
int next,to;
int value;
}edge[N*2];
void add(int u,int v,int w){
edge[++en].to = v;
edge[en].next = head[u];
head[u] = en;
edge[en].value = w;
}
void dfs(int u,int father){
depth[u] = depth[father]+1;
fa[u][0] = father;
for(int i=1;(1<<i)<=depth[u];i++){
fa[u][i] = fa[fa[u][i-1]][i-1];
}
for(int p=head[u]; p ;p=edge[p].next){
int v = edge[p].to ;
if(v!=father){
val[v] = val[u]+edge[p].value ;
dfs(v,u);
}
}
}
int lca(int u,int v){
if(depth[u]<depth[v]) swap(u,v);
for(int i=17;i>=0;i--){
if(depth[fa[u][i]]>=depth[v])
u = fa[u][i];
}
if(u==v) return u;
for(int i=17;i>=0;i--){
if(fa[u][i]!=fa[v][i]){
u = fa[u][i];
v = fa[v][i];
}
}
return fa[u][0];
}
int solve(int u,int v){
if(depth[u]<depth[v]) swap(u,v);
int x = lca(u,v);
return val[u]+val[v]-2*val[x];
}
void query(int u,int v,int w){//uvw是节点啦
int x = lca(u,v);
int y = lca(u,w);
int z = lca(v,w);
int ans = 0;
if(depth[x]>=depth[y] && depth[x]>=depth[z]){
ans = solve(u,v);
ans += solve(w,lca(u,v));
}
else if(depth[y]>=depth[x] && depth[y]>=depth[z]){
ans = solve(u,w);
ans += solve(v,lca(u,w));
}
else if(depth[z]>=depth[x] && depth[z]>=depth[y]){
ans = solve(v,w);
ans += solve(u,lca(v,w));
}
printf("%d\n",ans);
return ;
}
int main(){
scanf("%d",&n);
for(int i=1;i<n;i++){
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
add(b,a,c);
}
dfs(1,0);
scanf("%d",&q);
for(int i=1;i<=q;i++){
scanf("%d%d%d",&a,&b,&c);
query(a,b,c);
}
return 0;
}
一开始的算法有缺陷……并不是那样做的……打了一些草稿后才明白是应该分这样几种情况
统一的做法是每次找到三个lca中最深的一个,然后爬上去,爬上去后处理另一个。
Marinia
#include<iostream>
using namespace std;
const int N = 100005;
int n,m,x,y,t,k,a,b;
int fa[N][21] = {0},depth[N] = {0};//果然……这里超出范围了,写21而不是20
int tot;
int good[N][32] = {0};
bool need[32] = {0};
struct Edge{
int to,next;
}edge[N*2];
int head[N] = {0};
int en = 0;
void add(int u,int v){
edge[++en].to = v;
edge[en].next = head[u];
head[u] = en;
}
void dfs(int u,int father){
fa[u][0] = father;
depth[u] = depth[father] +1;
for(int i=0;i<m;i++){
good[u][i] += good[father][i];//从最顶上到自己(含自己)的货物量
}
for(int i=1;(1<<i)<=depth[u];i++){
fa[u][i] = fa[fa[u][i-1]][i-1];
}
for(int p=head[u];p;p=edge[p].next){
int v = edge[p].to ;
if(v!=father) dfs(v,u);
}
}
bool query(int u,int v){
int tmpu = u,tmpv = v;
for(int i=0;i<m;i++){
u = tmpu;
v = tmpv;
if(need[i]==false) continue;
bool f = false;
if(depth[u]<depth[v]) swap(u,v);
for(int j=20;j>=0;j--){
if(depth[fa[u][j]]>=depth[v]){//可以上跳
if(good[u][i]-good[fa[u][j]][i]>0) f = true;
u = fa[u][j];
}
}
if(f==true) continue;
if(u==v) {
if(good[u][i]-good[fa[u][0]][i]>0) f = true;
if(f) continue;
if(!f) return false;
}
for(int j=20;j>=0;j--){
if(fa[u][j]!=fa[v][j]){
if(good[u][i]-good[fa[u][j]][i]>0) f = true;
if(good[v][i]-good[fa[v][j]][i]>0) f = true;
u = fa[u][j];
v = fa[v][j];
}
}
if(good[u][i]-good[fa[u][1]][i]>0) f = true;
if(good[v][i]-good[fa[v][1]][i]>0) f = true;
if(!f) return false;
}
return true;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&tot);
for(int j=1;j<=tot;j++){
scanf("%d",&x);
good[i][x]++;
}
}
for(int i=1;i<n;i++){
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
dfs(1,0);
scanf("%d",&k);
for(int i=1;i<=k;i++){
scanf("%d%d%d",&a,&b,&t);
for(int j=0;j<32;j++){
need[j] = 0;
}
for(int j=1;j<=t;j++){
scanf("%d",&x);//小心变量被你自己重复使用,还不如多开几个……
need[x] = true;
}
if(query(a,b)) printf("YES\n");
else printf("NO\n");
}
return 0;
}
考前没有复习,导致考场上现场回忆链式前向星,写的是一个歪七扭八的版本,两个小时才拿了十分……
和前面的tips差不多:
- 记得开
fa[N][21]
,否则会超出范围,导致出错,具体出错的机理还不明确,只知道在找lca的途中会在第一层循环里面进行很多遍,应该是到20下标那里胡乱进行了一些赋值 - 呃呃呃呃我很喜欢在输入变量多的时候进行一些变量的共用,可是很容易出错因为后面的可能会把前面的覆盖掉……fine,以后小心一点为好
树上路径交叉
#include<iostream>
using namespace std;
const int N = 100005;
int T,n,m,x,y,x1,y1,x2,y2;
int head[N] = {0},depth[N] = {0};
int fa[N][18] = {0};
struct Edge{
int to,next;
}edge[N*2];
int en;
void add(int u,int v){
edge[++en].to = v;
edge[en].next = head[u];
head[u] = en;
}
void dfs(int u,int father){
depth[u] = depth[father]+1;
fa[u][0] = father;
for(int i=1;(1<<i)<=depth[u];i++){
fa[u][i] = fa[fa[u][i-1]][i-1];
}
for(int p=head[u];p;p=edge[p].next){
int v = edge[p].to ;
if(v != father){
dfs(v,u);
}
}
}
int lca(int u,int v){
if(depth[u]<depth[v]) swap(u,v);
for(int i=17;i>=0;i--){
if(depth[fa[u][i]]>=depth[v]){
u = fa[u][i];
}
}
if(u==v) return u;
for(int i=17;i>=0;i--){
if(fa[u][i]!=fa[v][i]){
u = fa[u][i];
v = fa[v][i];
}
}
return fa[u][0];
}
bool check(int u1,int v1,int u2,int v2){
int lca1 = lca(u1,v1);
int lca2 = lca(u2,v2);
if((lca(lca1,u2)==lca1 || lca(lca1,v2)==lca1) && depth[lca1]>=depth[lca2]) return true;
if((lca(lca2,u1)==lca2 || lca(lca2,v1)==lca2) && depth[lca2]>=depth[lca1]) return true;
return false;
}
int main(){
scanf("%d",&T);
while(T--){
en = 0;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
head[i] = 0;
for(int j=0;j<18;j++){
fa[i][j] = 0;
}
}
for(int i=1;i<n;i++){
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
dfs(1,0);
for(int i=1;i<=m;i++){
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
if(check(x1,y1,x2,y2))
printf("YES\n");
else printf("NO\n");
}
}
return 0;
}
这里的思路是,若其中一条链的lca在另一条链上,则路径有交叉。而一点在链上有两种写法,第一种是:(lca(x,u)==x||lca(x,v)==x)&&depth[x]<=lca(u,v)
;第二种是:dis(u,v)==dis(x,u)+dis(x,v)
试试第二种哈:
int dis(int u,int v){
return depth[u]+depth[v]-2*depth[lca(u,v)];
}
bool check(int u1,int v1,int u2,int v2){
int lca1 = lca(u1,v1);
int lca2 = lca(u2,v2);
if(dis(lca1,u2)+dis(lca1,v2)==dis(u2,v2)) return true;
if(dis(lca2,u1)+dis(lca2,v1)==dis(u1,v1)) return true;
return false;
}