E Merging Towers
题意:n个圆盘,堆成m堆,小的在上面。m-1次操作,每次选择两个堆a, b, 将b堆合并到a堆。每一次操作后输出当前状态下,如果想要合成一堆所需最少操作步数(合并的过程中始终保持小的盘子在大的盘子上面)。
思路:最少操作步数即是统计有多少对相邻的数不在同一堆。可以转化成确定相邻的两个数字合并到一起的时间,然后累加到相应时间点上,求前缀和即可求出答案。发现合并的操作过程即是建树的过程,那么也就转化成求LCA的问题。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=2e5+10;
struct EDGE{
int to,nxt;
EDGE(){}
EDGE(int a,int b) {to=a; nxt=b;}
}edge[N*2];
int d[N],t[N],fa[N*2][21],len[N*2],tt[N*2],c[N];
int tot;
void addedge(int x,int y)
{
edge[++tot]=EDGE(y,tt[x]);
tt[x]=tot;
}
int lca(int x,int y)
{
if(len[x]>len[y]) swap(x,y);
for(int i=20;i>=0;i--)
if(len[y]-len[x]>=(1<<i))
y=fa[y][i];
if(x==y) return x;
for(int i=20;i>=0;i--)
if(fa[x][i]!=fa[y][i])
{
x=fa[x][i];
y=fa[y][i];
}
return fa[x][0];
}
void dfs(int x)
{
for(int p=tt[x];p>0;p=edge[p].nxt)
{
int y=edge[p].to;
len[y]=len[x]+1;
dfs(y);
}
}
int main()
{
int n,m,cnt,a,b;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&d[i]);
for(int i=1;i<=m;i++) t[i]=i;
cnt=m;
for(int i=2;i<=m;i++)
{
scanf("%d%d",&a,&b);
cnt++;
fa[t[a]][0]=cnt;
fa[t[b]][0]=cnt;
addedge(cnt,t[a]);
addedge(cnt,t[b]);
t[a]=cnt;
}
dfs(cnt);
for(int j=1;j<=20;j++)
for(int i=1;i<=cnt;i++)
fa[i][j]=fa[fa[i][j-1]][j-1];
for(int i=1;i<n;i++)
{
int x=lca(d[i],d[i+1]);
if(x>m) x-=m;
else x=0;
c[x]++;
}
for(int i=m-1;i>=0;i--) c[i]+=c[i+1];
for(int i=1;i<=m;i++) printf("%d\n",c[i]);
return 0;
}