树(更新中....)
树
Link Cut Centroids
思路:树的重心,判断一个重心和两个重心的情况。当存在两个重心时,两个重心必相邻。只需把其中一个重心的边连在另一个重心上即可。
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
using namespace std;
const int maxn = 1e5+10;
int n;
int sz[maxn];
int sum[maxn];
int fa[maxn];
int r1,r2;
int mx=1e9;
vector <int > g[maxn];
void dfs(int u,int f)
{
sz[u]=1;
sum[u]=0;
for(auto v:g[u])
{
if(v==f) continue;
dfs(v,u);
sz[u]+=sz[v];
sum[u]=max(sum[u],sz[v]);
}
sum[u]=max(sum[u],n-sz[u]);
if(sum[u]==mx)
{
r2=u;
}
if(sum[u]<mx)
{
r1=u;
r2=0;
mx=sum[u];
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int t;
cin>>t;
while(t--)
{
r1=r2=0;
cin>>n;
for(int i=0;i<=n;i++)
{
mx=1e9;
sz[i]=sum[i]=fa[i]=0;
g[i].clear();
}
for(int i=1;i<n;i++)
{
int x,y;
cin>>x>>y;
g[x].push_back(y);
g[y].push_back(x);
}
dfs(1,-1);
if(!r2)
{
int u=r1;
int v=g[u][0];
cout<<u<<" "<<v<<endl;
cout<<u<<" "<<v<<endl;
}
else
{
int v;
for(int i=0;i<g[r2].size();i++)
{
if(g[r2][i]!=r1)
{
v=g[r2][i];
break;
}
}
cout<<r2<<" "<<v<<endl;
cout<<r1<<" "<<v<<endl;
}
}
}
核心城市
P5536 【XR-3】核心城市 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:显然求与核心城市的距离最大的城市,其与核心城市的距离最小那么k座城市一定在树的直径上。然后我们以这个直径的中点为根,把其他节点按照以这个节点为根的子树中节点的最大深度-这个点的深度排序选前$k−1$个节点即可
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
using namespace std;
const int maxn = 1e5+10;
int n,k;
int c;
int r1,r2;
int d[maxn];
int md[maxn];
int fa[maxn];
int ans[maxn];
vector <int > g[maxn];
vector <int > path;
void dfs(int u,int f)
{
for(auto v:g[u])
{
if(v==f) continue;
d[v]=d[u]+1;
fa[v]=u;
if(d[v]>d[c]) c=v;
dfs(v,u);
}
}
void dfs1(int u,int f)
{
md[u]=d[u];
for(auto v:g[u])
{
if(v==f) continue;
d[v]=d[u]+1;
dfs1(v,u);
md[u]=max(md[u],md[v]);
}
}
bool cmp(int a,int b)
{
return a>b;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n>>k;
for(int i=1;i<n;i++)
{
int x,y;
cin>>x>>y;
g[x].push_back(y);
g[y].push_back(x);
}
dfs(1,-1);
r1=c;
d[c]=0,dfs(c,-1);
r2=c;
for(int i=1;i<=(d[c]+1)/2;i++) r2=fa[r2];
d[r2]=0;
dfs1(r2,-1);
for(int i=1;i<=n;i++) ans[i]=md[i]-d[i];
sort(ans+1,ans+1+n,cmp);
int sum=0;
for(int i=k+1;i<=n;i++) sum=max(sum,ans[i]+1);
cout<<sum<<endl;
}
Three Paths on a Tree
思路:首先这三个点中其中两个点一定是这棵树直径的端点,其次在直径外找一点离两个端点距离最远即可
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
using namespace std;
const int maxn = 2e5+10;
vector <int > g[maxn];
int n,c;
int d[maxn];
int d1[maxn];
int d2[maxn];
int fa[maxn];
int vis[maxn];
int ans3;
int ans=0;
void dfs(int u,int f)
{
for(auto v:g[u])
{
if(v==f) continue;
d[v]=d[u]+1;
fa[v]=u;
if(d[v]>d[c]) c=v;
dfs(v,u);
}
return ;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n;
for(int i=1;i<n;i++)
{
int a,b;
cin>>a>>b;
g[a].push_back(b);
g[b].push_back(a);
}
dfs(1,-1);
int ans1=c;
d[c]=0,dfs(c,-1);
int ans2=c;
for(int i=1;i<=n;i++) d1[i]=d[i];
d[ans2]=0;
dfs(ans2,-1);
for(int i=1;i<=n;i++) d2[i]=d[i];
for(int i=1;i<=n;i++)
{
if(i==ans1||i==ans2) continue;
if(d1[i]+d2[i]>d1[ans3]+d2[ans3]) ans3=i;
}
ans=d1[ans3]+d2[ans3]+d1[ans2];
ans/=2;
cout<<ans<<endl;
cout<<ans1<<" "<<ans2<<" "<<ans3<<endl;
}
逃学的小孩
[P4408 NOI2003] 逃学的小孩 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:既然要要求耗时最长,那么就假定AB两点恰好为该树直径的端点,然后遍历找耗时最大的点即可
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
using namespace std;
const int maxn = 2e5+10;
typedef long long ll;
struct edge{
int v,w,net;
}e[maxn*2];
int head[maxn];
int cnt;
int n,m;
int r1,r2;
int c;
ll d[maxn];
ll d1[maxn];
ll d2[maxn];
int fa[maxn];
void add(int u,int v,int w)
{
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].net=head[u];
head[u]=cnt;
}
void dfs(int u,int ff)
{
for(int i=head[u];i;i=e[i].net)
{
int v=e[i].v;
if(v==ff) continue;
d[v]=d[u]+e[i].w;
if(d[v]>d[c]) c=v;
dfs(v,u);
}
return ;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n>>m;
for(int i=1;i<n;i++)
{
int u,v,w;
cin>>u>>v>>w;
add(u,v,w);
add(v,u,w);
}
dfs(1,-1);
r1=c;d[c]=0;
dfs(c,-1);
r2=c;
for(int i=1;i<=n;i++) d1[i]=d[i];
d[c]=0;
dfs(c,-1);
for(int i=1;i<=n;i++) d2[i]=d[i];
ll ans=0;
for(int i=1;i<=n;i++)
{
ans=max(min(d1[i],d2[i])+d1[r2],ans);
}
cout<<ans<<endl;
}
树网的核
[P1099 NOIP2007 提高组] 树网的核 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:先找出该树的直径,对该直径进行尺取,计算每条路径的偏心距,取最大值
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include <queue>
#include <deque>
using namespace std;
const int maxn = 310;
struct edge{
int v,w,net;
}e[maxn*2];
int head[maxn];
int d[maxn];
int fa[maxn];
int vis[maxn];
int q[maxn];
int c,n,s;
int cnt;
void add(int u,int v,int w)
{
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].net=head[u];
head[u]=cnt;
}
void dfs(int u,int ff)
{
for(int i=head[u];i;i=e[i].net)
{
int v=e[i].v;
if(v==ff||vis[v]) continue;
fa[v]=u;
d[v]=d[u]+e[i].w;
if(d[v]>d[c]) c=v;
dfs(v,u);
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n>>s;
vector <int > path(n+1);
for(int i=1;i<n;i++)
{
int u,v,w;
cin>>u>>v>>w;
add(u,v,w);
add(v,u,w);
}
dfs(1,-1);
d[c]=0,fa[c]=0;dfs(c,-1);
int r=c;
int ans=1e9;
for(int i=c,j=c;i;i=fa[i])
{
while(d[j]-d[i]>s) j=fa[j];
int x=max(d[c]-d[j],d[i]);
ans=min(ans,x);
}
for(int i=r;i;i=fa[i]) vis[i]=1;
for(int i=r;i;i=fa[i])
{
c=i;
d[i]=0;
dfs(i,fa[i]);
}
for(int i=1;i<=n;i++) ans=max(ans,d[i]);
cout<<ans<<endl;
}
巡逻
思路:题目意思大致是构建一个或两个环,在一颗树中从一个点开始遍历所有点并返回的代价为$2(n-1)$。当$k=1$时,只要将该树的直径两个端点链接便可的到最小值$2(n-1)-d1+1$。当$k=2$,在$k=1$的操作下,找到除了直接$d1$外最长的边即可。只要将直径$d1$上的所有边权改为-1,然后用树上dp就直径,最后答案为$2*(n-1)-d1+1-d2+1$.
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include <cmath>
using namespace std;
const int maxn = 1e5+10;
struct node{
int v,w,net;
}e[2*maxn];
int head[maxn];
int tot;
int c;
int r1,r2;
int d[maxn];
int vis[maxn];
int d1[maxn],d2[maxn];
int ans;
int fa[maxn];
int fe[maxn];
void add(int u,int v,int w)
{
e[++tot].v=v;
e[tot].w=w;
e[tot].net=head[u];
head[u]=tot;
}
void dfs(int u,int ff)
{
for(int i=head[u];i;i=e[i].net)
{
int v=e[i].v;
if(v==ff||vis[v]) continue;
vis[v]=1;
fa[v]=u;
fe[v]=i;
d[v]=d[u]+e[i].w;
if(d[v]>d[c]) c=v;
dfs(v,u);
}
}
void dfs1(int u,int ff)
{
d1[u]=0,d2[u]=0;
for(int i=head[u];i;i=e[i].net)
{
int v=e[i].v;
if(v==ff||vis[v]) continue;;
vis[v]=1;
dfs1(v,u);
int t=d1[v]+e[i].w;
if(t>d1[u])
{
d2[u]=d1[u];
d1[u]=t;
}
else if(t>d2[u])
{
d2[u]=t;
}
}
ans=max(ans,d1[u]+d2[u]);
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int n,k;
cin>>n>>k;
for(int i=1;i<n;i++)
{
int x,y;
cin>>x>>y;
add(x,y,1);
add(y,x,1);
}
dfs(1,-1);
d[c]=0,r1=c,fa[c]=0;
memset(vis,0,sizeof vis);
dfs(c,-1);
r2=c;
if(k==1)
{
int s=2*(n-1)-d[c]+1;
cout<<s<<endl;
return 0;
}
for(int i=r2;i!=r1;i=fa[i])
{
int id=fe[i];
e[id].w=-1;
e[id+((id&1)?1:-1)].w=-1;
}
fa[1]=0;
memset(vis,0,sizeof vis);
dfs1(1,-1);
int s=0;
s=2*(n-1)-d[c]+1-ans+1;
cout<<s<<endl;
}
HXY造公园
P2195 HXY造公园 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:对于问题一,直接dfs找直径即可。对于问题二,用并查集维护连通块,若不在同一个连通快上,则新形成的连通快的直径为$dis[fy]=max(max((dis[fx]+1)/2+(dis[fy]+1)/2+1,dis[fy]),dis[fx]);$
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include <queue>
using namespace std;
const int maxn = 3e5+10;
vector <int > g[maxn];
int n,m,q;
int s=0;
int v=-1;
int dis[maxn];
int fa[maxn];
int vis[maxn];
int c;
int find(int x)
{
return fa[x]==x?fa[x]:fa[x]=find(fa[x]);
}
void merge(int x,int y)
{
int fx=find(x);
int fy=find(y);
if(fx==fy) return ;
fa[fx]=fy;
}
void dfs(int u,int f,int d)
{
if(d>v) v=d,c=u;
for(auto v:g[u])
{
if(v==f) continue;
dfs(v,u,d+1);
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n>>m>>q;
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=m;i++)
{
int x,y;
cin>>x>>y;
g[x].push_back(y);
g[y].push_back(x);
merge(x,y);
}
for(int i=1;i<=n;i++)
{
if(fa[i]!=i) continue;
v=-1;
dfs(i,-1,0);
v=-1;
dfs(c,-1,0);
dis[i]=v;
}
while(q--)
{
int op;
cin>>op;
if(op==1)
{
int x;
cin>>x;
cout<<dis[find(x)]<<endl;
}
else
{
int x,y;
cin>>x>>y;
int fx=find(x);
int fy=find(y);
if(fx==fy) continue;
dis[fy]=max(max((dis[fx]+1)/2+(dis[fy]+1)/2+1,dis[fy]),dis[fx]);
merge(x,y);
}
}
return 0;
}
直径
思路:先找到该树的直径,并记录路径上的点,然后从路径上每一个点再次dfs,然后从每个点再次DFS
显然如果从某个点出发,能找到第二条与当前一边直径长度相等的一条路径,那么这条边就为可行边
分别从左右出发找一下即可。
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include <cmath>
using namespace std;
typedef long long ll;
const int maxn = 2e5+10;
struct edge{
int v;
ll w;
int net;
}e[maxn*2];
int head[maxn];
ll sum[maxn];
ll val[maxn];
int arr[maxn];
int vis[maxn];
int tot;
int fa[maxn];
int c,r1,r2;
ll d[maxn];
void add(int u,int v,ll w)
{
e[++tot].v=v;
e[tot].w=w;
e[tot].net=head[u];
head[u]=tot;
}
void dfs(int u,int ff)
{
for(int i=head[u];i;i=e[i].net)
{
int v=e[i].v;
if(v==ff||vis[v]) continue;
d[v]=d[u]+e[i].w;
fa[v]=u;
if(d[v]>d[c]) c=v;
dfs(v,u);
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int n;
cin>>n;
for(int i=1;i<n;i++)
{
int u,v; ll w;
cin>>u>>v>>w;
add(u,v,w);add(v,u,w);
}
dfs(1,-1);
d[c]=0,fa[c]=0,r1=c;
dfs(c,-1);
r2=c;
int cnt=0;
for(int i=r2;i;i=fa[i])
{
vis[i]=1;
arr[++cnt]=i;
}
reverse(arr+1,arr+1+cnt);
for(int i=1;i<=cnt;i++)
{
sum[i]=d[arr[i]];
}
for(int i=1;i<=cnt;i++)
{
d[arr[i]]=0;
c=arr[i];
dfs(arr[i],-1);
val[i]=d[c];
}
int l=1,r=cnt;
for(int i=1;i<=cnt;i++)
{
if(val[i]==sum[i]) l=i;
}
for(int i=cnt;i>=1;i--)
{
if(val[i]==sum[cnt]-sum[i]) r=i;
}
cout<<sum[cnt]<<endl<<r-l<<endl;
}
次小生成树
思路:次小生成树一定仅有一条边和最小生成树不同,首先生成一棵最小生成树,然后枚举剩余的边,加到最小生成树中。此时树中会形成一个换环,只要找出路径中的最大的边和最小的边,然后计算结果即可。
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
using namespace std;
typedef long long ll;
#define inf 1e16
const int maxn = 3e5+10;
struct edge{
int v,w,net;
}e[maxn<<1];
struct node{
int u,v,w;
}s[maxn];
int n,m;
int head[maxn<<1];
int tot;
int d[maxn];
int vis[maxn<<1];
int f[maxn][33];
int fa[maxn<<1];
ll dp[2][maxn][33];
int dis[maxn];
int deep;
ll val1,val2;
ll ans,ans_max;
bool cmp(node a,node b)
{
return a.w<b.w;
}
int find(int x)
{
return fa[x]==x?fa[x]:fa[x]=find(fa[x]);
}
void add(int u,int v,int w)
{
e[++tot].v=v;
e[tot].w=w;
e[tot].net=head[u];
head[u]=tot;
}
void kruskal()
{
sort(s+1,s+1+m,cmp);
for(int i=1;i<=m;i++)
{
int x=find(s[i].u),y=find(s[i].v);
if(x==y) continue;
vis[i]=1;
fa[x]=y;
ans+=s[i].w;
add(s[i].u,s[i].v,s[i].w);
add(s[i].v,s[i].u,s[i].w);
}
}
void dfs(int u,int pre)
{
for(int i=head[u];i;i=e[i].net)
{
int v=e[i].v;
if(d[v]||v==pre) continue;
f[v][0]=u;
dp[0][v][0]=e[i].w;
dp[1][v][0]=-inf;
d[v]=d[u]+1;
dfs(v,u);
}
}
void st(int n)
{
for(int j=1;j<=31;j++)
{
for(int i=1;i<=n;i++)
{
f[i][j]=f[f[i][j-1]][j-1];
if(dp[0][i][j-1]!=dp[0][f[i][j-1]][j-1])
{
dp[0][i][j]=max(dp[0][i][j-1],dp[0][f[i][j-1]][j-1]);
dp[1][i][j]=min(dp[0][i][j-1],dp[0][f[i][j-1]][j-1]);
}
else
{
dp[0][i][j]=dp[0][i][j-1];
dp[1][i][j]=max(dp[1][i][j-1],dp[1][f[i][j-1]][j-1]);
}
}
}
}
void update2(int x)
{
if(x>val1)
val2=val1,val1=x;
else if(x>val2&&x!=val1)
val2=x;
}
void update(int x,int t)
{
update2(dp[0][x][t]);
update2(dp[1][x][t]);
}
void lca(int u,int v)
{
val1=val2=-inf;
if(d[u]<d[v])
swap(u,v);
for(int i=31;i>=0;i--)
{
if(d[f[u][i]]>=d[v])
{
update(u,i);
u=f[u][i];
}
}
if(u==v) return ;
for(int i=31;i>=0;i--)
{
if(f[u][i]!=f[v][i])
{
update(u,i);update(v,i);
u=f[u][i];
v=f[v][i];
}
}
update(u,0),update(v,0);
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int u,v,w;
cin>>u>>v>>w;
s[i]={u,v,w};
fa[i]=i;
}
kruskal();
d[1]=1;
dfs(1,0);
st(n);
ans_max=inf;
for(int i=1;i<=m;i++)
{
if(vis[i]) continue;
lca(s[i].u,s[i].v);
if(val1!=s[i].w)
ans_max=min(ans_max,ans-val1+s[i].w);
else
ans_max=min(ans_max,ans-val2+s[i].w);
}
cout<<ans_max<<endl;
return 0;
}
闇の連鎖
思路:
首先主要边刚好为一棵树,当对一棵树加上一条边时便形成了一个环。考虑枚举所有次要边,当加入一条边时,树上形成环的路径都加一,表示需要切掉一条次要边才能变为不连通。这时用树上差分进行修改操作即可
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <cmath>
#include <vector>
using namespace std;
const int maxn = 3e5+10;
struct edge{
int v,net;
}e[maxn<<1];
int head[maxn];
int tot;
int d[maxn];
int dis[maxn];
int ans=0;
int n,m;
int vis[maxn];
int f[maxn][33];
void add(int u,int v)
{
e[++tot].v=v;
e[tot].net=head[u];
head[u]=tot;
}
void dfs(int u,int pre)
{
d[u]=d[pre]+1;
f[u][0]=pre;
for(int i=head[u];i;i=e[i].net)
{
int v=e[i].v;
if(d[v]||v==pre) continue;
dfs(v,u);
}
}
void st(int n)
{
for(int j=1;j<=16;j++)
{
for(int i=1;i<=n;i++)
{
f[i][j]=f[f[i][j-1]][j-1];
}
}
}
int lca(int x,int y)
{
if(d[x]<d[y]) swap(x,y);
for(int i=16;i>=0;i--)
{
if(d[f[x][i]]>=d[y]) x=f[x][i];
}
if(x==y) return x;
for(int i=16;i>=0;i--)
{
if(f[x][i]!=f[y][i])
{
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];
}
int dfs1(int u,int pre)
{
int res=dis[u];
for(int i=head[u];i;i=e[i].net)
{
int v=e[i].v;
if(v==pre) continue;
int s=dfs1(v,u);
if(s==0) ans+=m;
else if(s==1) ans++;
res+=s;
}
return res;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n>>m;
for(int i=1;i<n;i++)
{
int u,v;
cin>>u>>v;
add(u,v);add(v,u);
}
dfs(1,0);
st(n);
for(int i=1;i<=m;i++)
{
int u,v;
cin>>u>>v;
int p=lca(u,v);
dis[u]++;dis[v]++;dis[p]-=2;
}
dfs1(1,-1);
printf("%d\n",ans);
}
斐波那契
P3938 斐波那契 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:一眼感觉lca模板题,但是点的数据大小到了1e12。仔细观察该兔子祖先关系图,再联系题目。发现每个节点和父节点的差值正好是一个斐波那契数,且深度越大差值越大。因此先需处理出数据范围内的斐波那契数,只需要找斐波那契中最大的但是比他小的那个数就好了,然后不断减掉,向上找。
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include <cmath>
using namespace std;
const int maxn = 3e5+10;
typedef long long ll;
ll dp[maxn];
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int m;
cin>>m;
dp[0]=dp[1]=1;
for(int i=2;i<=60;i++)
dp[i]=dp[i-1]+dp[i-2];
while(m--)
{
ll x,y;
cin>>x>>y;
while(x!=y)
{
if(y<x) swap(x,y);
int l=1,r=60;
while(l<=r)
{
int mid = (l+r)/2;
if(dp[mid]<y) l=mid+1;
else r=mid-1;
}
y-=dp[l-1];
}
printf("%lld\n",x);
}
}
紧急集合
[P4281 AHOI2008]紧急集合 / 聚会 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:分别找三个点两两间的lca,然后以一个不同个lca点为集合点
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include <cmath>
using namespace std;
const int maxn = 5e5+10;
struct edge{
int v,net;
}e[maxn<<1];
int head[maxn];
int tot;
int d[maxn];
int f[maxn][33];
int ans=0;
void add(int u,int v)
{
e[++tot].v=v;
e[tot].net=head[u];
head[u]=tot;
}
void dfs(int u,int pre)
{
f[u][0]=pre;
d[u]=d[pre]+1;
for(int i=head[u];i;i=e[i].net)
{
int v=e[i].v;
if(v==pre||d[v]) continue;
dfs(v,u);
}
}
void st(int n)
{
for(int j=1;j<=16;j++)
for(int i=1;i<=n;i++)
f[i][j]=f[f[i][j-1]][j-1];
}
int lca(int x,int y)
{
if(d[x]<d[y]) swap(x,y);
for(int i=16;i>=0;i--)
{
if(d[f[x][i]]>=d[y])
{
x=f[x][i];
}
}
if(x==y) return x;
for(int i=16;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(0);
cin.tie(0);
int n,m;
cin>>n>>m;
for(int i=1;i<n;i++)
{
int a,b;
cin>>a>>b;
add(a,b),add(b,a);
}
dfs(1,0);
st(n);
while(m--)
{
int x,y,z;
cin>>x>>y>>z;
int l1=lca(x,y);
int l2=lca(x,z);
int l3=lca(z,y);
if (l1 == l2)
{
cout << l3 << " ";
cout << d[x] - 2*d[l1] + d[y] + d[z] - d[l3] << endl;
continue;
}
else if (l2 == l3)
{
cout << l1 << " ";
cout << d[x] + d[y] + d[z] -2* d[l2] - d[l1] << endl;
continue;
}
else
{
cout << l2 << " ";
cout << d[x] + d[y] + d[z] -2* d[l3] - d[l2] << endl;
continue;
}
}
}
树殓刨坟
天天爱跑步
[P1600 NOIP2016 提高组] 天天爱跑步 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:首先考虑从起点u到lca的路径上,若路径上存在一点x对答案有贡献,那么$d[s]-d[x]=w[x]$即当$d[x]+w[x]=d[u]$时,$ans[x]++$。这时我们用一个数组统计起点u到lca路径上所有满足$d[x]+w[x]=d[u]$条件的点贡献加1。然后考虑从lca到终点v的路径上,若路径上存在一点x对答案有贡献,那么即当$d[u]+d[x]−2∗d[LCA]=w[x]$时,即$d[x]-w[x]=2d[lca]-d[u]$时,$ans[x]++$。这时我们用一个数组统计lca到终点v上所有满足$d[x]-w[x]=2d[lca]-d[u]$的点贡献加1。即用树上差分进行维护,注意$2*d[lca]-d[u]$可能为负数,我们可以加一个偏移量。最后在dfs每个点记录其对答案的贡献即可。
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
using namespace std;
const int maxn = 3e5+10;
int n,m;
struct edge{
int v,net;
}e[maxn<<1];
int head[maxn];
int tot;
int d[maxn];
int d1[maxn<<1],d2[maxn<<1];
int ans[maxn];
vector <pair <int ,int > > op1[maxn],op2[maxn];
int f[maxn][33];
int w[maxn];
void add(int u,int v)
{
e[++tot].v=v;
e[tot].net=head[u];
head[u]=tot;
}
void dfs(int u,int pre)
{
d[u]=d[pre]+1;
f[u][0]=pre;
for(int i=head[u];i;i=e[i].net)
{
int v=e[i].v;
if(d[v]||v==pre) continue;
dfs(v,u);
}
}
void st(int n)
{
for(int j=1;j<=16;j++)
for(int i=1;i<=n;i++)
f[i][j]=f[f[i][j-1]][j-1];
}
int lca(int x,int y)
{
if(d[x]<d[y]) swap(x,y);
for(int i=16;i>=0;i--)
{
if(d[f[x][i]]>=d[y]) x=f[x][i];
}
if(x==y) return x;
for(int i=16;i>=0;i--)
{
if(f[x][i]!=f[y][i])
{
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];
}
void update(int u,int v)
{
int p=lca(u,v);
op1[u].push_back(make_pair(d[u],1));
op1[f[p][0]].push_back(make_pair(d[u],-1));
op2[v].push_back(make_pair(d[u]-2*d[p]+n,1));
op2[p].push_back(make_pair(d[u]-2*d[p]+n,-1));
}
void dfs1(int u,int pre)
{
int v1=w[u]+d[u],v2=w[u]-d[u]+n;
int res1=d1[v1],res2=d2[v2];
for(int i=head[u];i;i=e[i].net)
{
int v=e[i].v;
if(v==pre) continue;
dfs1(v,u);
}
for(int i=0;i<op1[u].size();i++)
d1[op1[u][i].first]+=op1[u][i].second;
for(int i=0;i<op2[u].size();i++)
d2[op2[u][i].first]+=op2[u][i].second;
ans[u]=(d1[v1]-res1)+(d2[v2]-res2);
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n>>m;
for(int i=1;i<n;i++)
{
int u,v;
cin>>u>>v;
add(u,v);add(v,u);
}
for(int i=1;i<=n;i++) cin>>w[i];
dfs(1,0);
st(n);
while(m--)
{
int x,y;
cin>>x>>y;
update(x,y);
}
dfs1(1,0);
for(int i=1;i<=n;i++)
cout<<ans[i]<<" ";
}
运输计划
[P2680 NOIP2015 提高组] 运输计划 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:lca+树上差分+二分。题目是说找最大的最小,很容易可以想到用二分求解。假设在t秒内完成,则二分判断是否可以去掉一条边,使得所有路径在可以在t秒内完成。求每个路径的公共边可以用差分处理,路径上的边加一即可,最后遍历dfs序即可求出每条边出现多少次。同时还要考虑如何求树上某一条路径的权值。
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
using namespace std;
const int maxn = 3e5+10;
struct edge{
int v,w,net;
}e[maxn<<1];
int head[maxn];
int tot;
int dfn[maxn];
int d[maxn];
int d1[maxn];
int dis[maxn];
int f[maxn][21];
int l[maxn];
int cnt;
pair <int ,int> road[maxn];
int n,m;
void add(int u,int v,int w)
{
e[++tot].v=v;
e[tot].w=w;
e[tot].net=head[u];
head[u]=tot;
}
void dfs(int u,int pre,int dep)
{
dfn[++cnt]=u;
d[u]=dep;
f[u][0]=pre;
for(int i=head[u];i;i=e[i].net)
{
int v=e[i].v;
if(d[v]||v==pre) continue;
dis[v]=dis[u]+e[i].w;
dfs(v,u,dep+1);
}
}
void st(int n)
{
for(int j=1;j<=20;j++)
for(int i=1;i<=n;i++)
f[i][j]=f[f[i][j-1]][j-1];
}
int lca(int x,int y)
{
if(d[x]<d[y]) swap(x,y);
for(int i=20;i>=0;i--)
{
if(d[f[x][i]]>=d[y]) x=f[x][i];
}
if(x==y) return x;
for(int i=20;i>=0;i--)
{
if(f[x][i]!=f[y][i])
{
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];
}
bool check(int mid)
{
int s=0,maxx=0;
memset(d1,0,sizeof d1);
for(int i=1;i<=m;i++)
{
int x=road[i].first,y=road[i].second;
int p=l[i];
int dist=dis[x]+dis[y]-2*dis[p];
if(dist>mid)
{
d1[x]++,d1[y]++,d1[p]-=2;
maxx=max(maxx,dist-mid);
s++;
}
}
if(!s) return true;
for(int i=n;i>=1;i--)
{
int u=dfn[i];
d1[f[u][0]]+=d1[u];
}
for(int i=1;i<=n;i++)
{
if(d1[i]==s&&dis[i]-dis[f[i][0]]>=maxx)
return true;
}
return false;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n>>m;
for(int i=1;i<n;i++)
{
int a,b,t;
cin>>a>>b>>t;
add(a,b,t),add(b,a,t);
}
dfs(1,0,1);
st(n);
for(int i=1;i<=m;i++)
{
int u,v;
cin>>u>>v;
road[i]={u,v};
l[i]=lca(u,v);
}
int l=0,r=1e9;
while(l<=r)
{
int mid = l+r>>1;
if(check(mid)) r=mid-1;
else l=mid+1;
}
cout<<r+1<<endl;
}
松鼠的新家
思路:[P3258 JLOI2014]松鼠的新家 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:树上差分修改即可。
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
using namespace std;
const int maxn = 3e5+10;
struct edge{
int v,net;
}e[maxn<<1];
int head[maxn];
int f[maxn][22];
int d[maxn];
int s[maxn];
int ans[maxn];
int tot;
int n;
int a[maxn];
void add(int u,int v)
{
e[++tot].v=v;
e[tot].net=head[u];
head[u]=tot;
}
void dfs(int u,int pre)
{
d[u]=d[pre]+1;
f[u][0]=pre;
for(int i=head[u];i;i=e[i].net)
{
int v=e[i].v;
if(v==pre) continue;
dfs(v,u);
}
}
void st(int n)
{
for(int j=1;j<=20;j++)
for(int i=1;i<=n;i++)
f[i][j]=f[f[i][j-1]][j-1];
}
int lca(int x,int y)
{
if(d[x]<d[y]) swap(x,y);
for(int i=20;i>=0;i--)
{
if(d[f[x][i]]>=d[y])
x=f[x][i];
}
if(x==y) return x;
for(int i=20;i>=0;i--)
{
if(f[x][i]!=f[y][i])
{
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];
}
void dfs1(int u,int pre)
{
int sum=s[u];
for(int i=head[u];i;i=e[i].net)
{
int v=e[i].v;
if(v==pre) continue;
dfs1(v,u);
s[u]+=s[v];
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
for(int i=1;i<n;i++)
{
int u,v;
cin>>u>>v;
add(u,v);add(v,u);
}
dfs(1,0);
st(n);
int x=a[1],y=a[1];
for(int i=2;i<=n;i++)
{
x=y;y=a[i];
int p=lca(x,y);
s[x]++;s[y]++;
s[p]--;s[f[p][0]]--;
}
dfs1(1,0);
s[a[1]]++;
for(int i=1;i<=n;i++)
{
cout<<s[i]-1<<endl;
}
}
疫情控制
思路:题目是要求最小的最大值,因此可以考虑用二分答案求解。可以想到当军队越靠近根节点时,能管辖的叶子结点就越多。考虑军队的两种情况,一种是可以到达根节点,另一种是不能到达根节点。对于不能到达根节点的军队,最好的策略是让他在规定时间内尽可能走到深度越浅的节点。对于可以到达根节点的军队,考虑两种情况,一是是否跨越根节点去管辖其它子树,而是选择驻扎在当前节点。若一直军队当前在节点s,且在剩余的时间内无法从s节点到根节点在返回s节点的话,最优情况是让其驻扎当前节点,而不是由其它子树的军队来管辖s节点(详细证明可见蓝书)。队伍未缺点驻扎的军队求采取这个引理判断。这题思路比较简单,但是代码实现稍微复杂
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
using namespace std;
typedef long long ll;
typedef pair <ll,int> pli;
const int maxn = 5e4+10;
struct edge{
int v,w,net;
}e[maxn<<1];
int head[maxn];
int tot;
int f[maxn][30];
int dfn[maxn];
int d[maxn];
ll dis[maxn];
pli a[maxn];
int q[maxn];
int cover[maxn];
int son[maxn];
int ones[maxn];
int vis[maxn];
int cnt,snt;
int n,m;
void add(int u,int v,int w)
{
e[++tot].v=v;
e[tot].w=w;
e[tot].net=head[u];
head[u]=tot;
}
void dfs(int u,int pre)
{
d[u]=d[pre]+1;
f[u][0]=pre;
for(int i=head[u];i;i=e[i].net)
{
int v=e[i].v;
if(d[v]||v==pre) continue;
dis[v]=dis[u]+e[i].w;
dfs(v,u);
}
}
void st(int n)
{
for(int j=1;j<=20;j++)
for(int i=1;i<=n;i++)
f[i][j]=f[f[i][j-1]][j-1];
}
pli up(int x,ll mid)
{
for(int i=20;i>=0;i--)
{
if(f[x][i]>1&&dis[x]-dis[f[x][i]]<=mid)
{
mid-=dis[x]-dis[f[x][i]];
x=f[x][i];
}
}
return {mid,x};
}
void dfs1(int u,int pre)
{
bool flag1=true;
bool flag2=true;
for(int i=head[u];i;i=e[i].net)
{
int v=e[i].v;
if(v==pre) continue;
dfs1(v,u);
flag1 &= cover[v];
flag2=0;
if(u==1&&!cover[v]) son[++snt]=v;
}
cover[u]=ones[u]||(!flag2&&flag1);
}
bool cmp(int a,int b)
{
return dis[a]<dis[b];
}
bool check(ll mid)
{
memset(ones,0,sizeof ones);
memset(cover,0,sizeof cover);
memset(vis,0,sizeof vis);
cnt=0,snt=0;
for(int i=1;i<=m;i++)
{
pli ar=up(q[i],mid);
ll rest=ar.first;
int pos=ar.second;
if(rest<=dis[pos]) ones[pos]=1;
else a[++cnt]={rest-dis[pos],pos};
}
dfs1(1,-1);
sort(a+1,a+1+cnt);
for(int i=1;i<=cnt;i++)
{
ll rest=a[i].first;
int pos=a[i].second;
if(!cover[pos]&&rest<dis[pos])
{
cover[pos]=true;
vis[i]=true;
}
}
sort(son+1,son+1+snt,cmp);
for(int i=1,j=1;i<=snt;i++)
{
int pos=son[i];
if(cover[pos]) continue;
while(j<=cnt&&(vis[j]||a[j].first<dis[pos])) j++;
if(j>cnt) return false;
j++;
}
return true;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n;
ll s=0;
for(int i=1;i<n;i++)
{
int u,v,w;
cin>>u>>v>>w;
add(u,v,w);add(v,u,w);
s+=w;
}
dfs(1,0);
st(n);
cin>>m;
for(int i=1;i<=m;i++)
{
cin>>q[i];
}
ll l=0,r=s;
int flag=0;
while(l<r)
{
ll mid = l+r>>1;
if(check(mid))
{
flag=1;
r=mid;
}
else l=mid+1;
}
if(!flag) cout<<-1<<endl;
else cout<<r<<endl;
}
异象石
思路:本题有一个引理:对整棵树进行dfs,求出每个点的时间戳(dfs序),然后可以发现如果按照时间戳(dfs序)从小到大的顺序,把节点排成一圈(首尾相连),
然后累加相邻两个节点之间的路径长度,最后得到的结果恰好是所求答案的两倍。数学归纳法可证明。
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include <set>
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;
struct edge{
int v,w,net;
}e[maxn<<1];
int head[maxn];
int tot;
int d[maxn];
int f[maxn][22];
int dfn[maxn];
int pos[maxn];
ll dis[maxn];
int cnt=0;
int n,m;
set <int > s;
void add(int u,int v,int w)
{
e[++tot].v=v;
e[tot].w=w;
e[tot].net=head[u];
head[u]=tot;
}
void dfs(int u,int pre)
{
pos[++cnt]=u;
dfn[u]=cnt;
f[u][0]=pre;
d[u]=d[pre]+1;
for(int i=head[u];i;i=e[i].net)
{
int v=e[i].v;
if(v==pre) continue;
dis[v]=dis[u]+e[i].w;
dfs(v,u);
}
}
void ST(int n)
{
for(int j=1;j<=20;j++)
for(int i=1;i<=n;i++)
f[i][j]=f[f[i][j-1]][j-1];
}
int lca(int x,int y)
{
if(d[x]<d[y]) swap(x,y);
for(int i=20;i>=0;i--)
{
if(d[f[x][i]]>=d[y])
{
x=f[x][i];
}
}
if(x==y) return x;
for(int i=20;i>=0;i--)
{
if(f[x][i]!=f[y][i])
{
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];
}
ll getdis(int x,int y)
{
int p =lca(x,y);
return dis[x]+dis[y]-2*dis[p];
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n;
for(int i=1;i<n;i++)
{
int u,v,w;
cin>>u>>v>>w;
add(u,v,w),add(v,u,w);
}
dfs(1,0);
ST(n);
ll res=0;
cin>>m;
while(m--)
{
char op;
cin>>op;
if(op=='+')
{
int x;
cin>>x;
s.insert(dfn[x]);
auto it=s.find(dfn[x]);
auto lit=it,rit=it;
int l,r;
if(lit==s.begin())
{
lit=s.end();
lit--;
l=pos[*lit];
}
else
{
lit--;
l=pos[*lit];
}
if((++rit)==s.end())
{
rit=s.begin();
r=pos[*rit];
}
else
{
r=pos[*rit];
}
res=res+getdis(l,x)+getdis(x,r)-getdis(l,r);
}
else if(op=='-')
{
int x;
cin>>x;
auto it=s.find(dfn[x]);
auto lit=it,rit=it;
int l,r;
if(lit==s.begin())
{
lit=s.end();
lit--;
l=pos[*lit];
}
else
{
lit--;
l=pos[*lit];
}
if((++rit)==s.end())
{
rit=s.begin();
r=pos[*rit];
}
else
{
r=pos[*rit];
}
s.erase(it);
res=res-getdis(l,x)-getdis(x,r)+getdis(l,r);
}
else
{
cout<<res/2<<endl;
}
}
}
杰哥,你带我走吧!杰哥
C-杰哥,你带我走吧!杰哥_武汉科技大学第十一届程序设计校赛
思路:lca问题,预处理除不同k值的路径长度。
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <string>
#include <cstring>
using namespace std;
typedef long long ll;
const int mod = 1e9+7;
const int maxn = 1e5+5;
struct edge{
int v,net;
}e[maxn<<1];
struct dat {
int x, y, k;
int id, ans;
} nod[maxn];
int head[maxn];
int tot;
int d[maxn];
//int dis[51][maxn][18];
ll dis[maxn][51];
int f[maxn][18];
int a[maxn];
int w[51][maxn];
int fpow(ll a,int b)
{
ll res=1;
for(;b;b>>=1)
{
if(b&1)
{
res=res*a%mod;
}
a=a*a%mod;
}
return res;
}
void add(int u,int v)
{
e[++tot].v=v;
e[tot].net=head[u];
head[u]=tot;
}
void dfs(int u,int pre)
{
d[u]=d[pre]+1;
f[u][0]=pre;
for(int i=head[u];i;i=e[i].net)
{
int v=e[i].v;
if(v==pre) continue;
dfs(v,u);
}
}
void dfs(int u,int pre,int k)
{
// cout<<"AA"<<endl;
for(int i=head[u];i;i=e[i].net)
{
int v=e[i].v;
if(v==pre) continue;
dis[v][k]=(dis[u][k]+w[k][v])%mod;
dfs(v,u,k);
}
}
void st(int n)
{
for(int j=1;j<=17;j++)
{
for(int i=1;i<=n;i++)
{
f[i][j]=f[f[i][j-1]][j-1];
}
}
}
int lca(int x,int y)
{
if(d[x]<d[y])
swap(x,y);
for(int i=17;i>=0;i--)
{
if(d[f[x][i]]>=d[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()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
for(int i=1;i<=50;i++)
{
for(int j=1;j<=n;j++)
{
ll x=fpow(a[j],i);
w[i][j]=x;
}
}
for(int i=1;i<n;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
dfs(1,0);
st(n);
for(int i=1;i<=50;i++)
{
dfs(1,0,i);
}
while(m--)
{
int x,y,k;
scanf("%d%d%d",&x,&y,&k);
int p=lca(x,y);
// cout<<"p="<<p<<endl;
ll ans=(dis[x][k]+dis[y][k]-2*dis[p][k]+w[k][p]+2*mod)%mod;
cout<<ans<<endl;
}
}
雨天的尾巴
思路: