题目大意:给定一棵树,有两种操作:
1.询问某个点到根的路径上遇到的第一个黑色边的编号
2.将某条路径涂黑
题解:我先按照3910火车的思路写了个暴力程序,然后TLE了,膜了一发题解
首先将每条边归到它下面的点上,每个点代表其与父亲的连边,这样就可以让下面关于边的操作都用点代表
将所有操作正着进行一遍,把所有的黑边相邻的点按照关系合并,这样一个集合中的代表元素一定是深度最小的点
倒着做所有的操作,对于染色操作撤销相当于染白,按照边被染黑的时间排序,直接合并每一条边即可,这里为了优先合并自始至终都是白色的边,将其染色时间设为m+1
每次的查询操作直接找集合的代表元素,其代表的边就是答案
我的收获:强啊,跪啊
TLE
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int M=1000005;
int n,m,t;
int head[M],f[M],fa[M],dep[M],pre[M];
int read()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
struct edge{int to,nex,id;}e[M*4];
void add(int u,int v){e[t].to=v,e[t].nex=head[u],head[u]=t++;}
int fid(int x){return f[x]==x?x:f[x]=fid(f[x]);}
void uniom(int x,int y){int p=fid(x),q=fid(y);if(p!=q) f[x]=y;}
void dfs(int x){
for(int i=head[x];i!=-1;i=e[i].nex){
int v=e[i].to;
if(v!=fa[x]) pre[v]=i,fa[v]=x,dep[v]=dep[x]+1,dfs(v);
}
}
int query(int x){
while(fid(x)!=fid(1))
x=fa[x];
if(x==1) return 0;
return (pre[x]+1)/2;
}
void change(int p,int q){
while(fid(p)!=fid(q)){
if(dep[p]<dep[q]) swap(p,q);
f[p]=f[fa[p]],p=f[p];
}
}
void work()
{
int opt,x,y;
while(m--)
{
scanf("%d",&opt);
if(opt==1) x=read(),printf("%d\n",query(x));
if(opt==2) x=read(),y=read(),change(x,y);
}
}
void init()
{
int x,y;
cin>>n>>m;t=1;memset(head,-1,sizeof(head));
for(int i=1;i<n;i++)
x=read(),y=read(),add(x,y),add(y,x);
for(int i=1;i<=n;i++) f[i]=i;
dfs(1);
}
int main()
{
init();
work();
return 0;
}
AC
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
#define M 1000010
int n,m,t,now;
int f[M],sum[M],be[M],p[M],vl[M];
int head[M],pre[M],fa[M],dep[M];
struct edge{int to,nex,id;}e[M*4];
void add(int u,int v,int z){e[t]=(edge){v,head[u],z};head[u]=t++;}
int Find(int x){return f[x]==x?x:f[x]=Find(f[x]);}
void dfs(int x,int father){
for(int i=head[x];i!=-1;i=e[i].nex){
int v=e[i].to;
if(v==father) continue;
pre[v]=e[i].id,fa[v]=x,dep[v]=dep[x]+1;
dfs(v,x);
}
}
void up(int x,int y,int k){//正着进行合并操作,这里合并的是点
x=Find(x),y=Find(y);
while(x!=y){
if(dep[x]<dep[y]) swap(x,y);
if(be[x]!=m+1) x=f[x];
else be[x]=k,f[x]=f[fa[x]],x=f[x];
}
}
void work()
{
for(int i=1;i<=n;i++) sum[be[i]]++;//对be[i]基数排序
for(int i=2;i<=m+1;i++) sum[i]+=sum[i-1];
for(int i=1;i<=n;i++) vl[sum[be[i]]--]=i;//vl[i]表示第i个被染色的点的编号
for(int i=1;i<=n;i++) f[i]=i;
for(int i=m+1;i>=1;i--){//倒着处理t=i的染色,白边视为t=m+1
if(p[i]!=-1) p[i]=pre[Find(p[i])];
else for(int x;be[x=vl[now]]==i&&now;now--) f[x]=f[fa[x]];
}
for(int i=1;i<=m;i++) if(p[i]!=-1) printf("%d\n",p[i]);
}
void init()
{
int x,y,opt;
t=0;memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);now=n;
for(int i=1;i<n;i++) scanf("%d%d",&x,&y),add(x,y,i),add(y,x,i);//记录编号
for(int i=1;i<=n;i++) f[i]=i,be[i]=m+1;//be[i]表示点i代表的边被染黑的时间
dfs(1,0);memset(p,-1,sizeof(p));
for(int i=1;i<=m;i++){
scanf("%d",&opt);
if(opt==2) scanf("%d%d",&x,&y),up(x,y,i);
else scanf("%d",&x),p[i]=x;//p[i]表示第i次操作询问的点
}
}
int main()
{
init();
work();
return 0;
}