去题面的传送门
第一反应是并查集维护战舰之间的集合关系,记录每一个战舰前面的战舰数量pre[i],记录每一个并查集的集合大小rank[i],每次合并更新pre的大小,查询时如果两艘战舰在不同的集合里,直接return -1。反之,返回两艘战舰的pre之差-1,就是两艘战舰之间的战舰数量
但是对于如何维护pre,我调试了很久。
一开始想的是,每次合并时,把排列在后面的那个集合的根节点的pre+前面的集合的集合大小,在路径压缩时顺便求一下子节点的pre[x]=max(pre[x],pre[fa[x]]+1)。但是这样肯定有bug啊,一旦将一个集合路径压缩完成,他原先的树结构就打破了,所以这个式子是不对的。
然后我又记录了一个son[i]表示每个节点在路径压缩之前的儿子。写一个findson函数,每次合并时,都把排在后面的集合按照son把它所在的集合的子节点遍历一遍,改变他们的pre值,这样就不会破坏原来的结构了。
这是这种做法的代码(61分):
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=30000+10;
int t;
int fa[maxn],rank[maxn],son[maxn],pre[maxn];
int findfa(int x)
{
if(fa[x]==x) return x;
return fa[x]=findfa(fa[x]);
}
int findson(int x,int v)
{
pre[x]+=v;
if(son[x]==x) return x;
return findson(son[x],v);
}
void merge(int x,int y)//x合并到y上
{
int fx=findfa(x),fy=findfa(y);
fa[fx]=fy;
son[findson(fy,0)]=fx;
findson(fx,rank[fy]);
rank[fy]+=rank[fx];
}
int ask(int x,int y)
{
int fx=findfa(x),fy=findfa(y);
if(fx!=fy) return -1;
return max(pre[y],pre[x])-min(pre[x],pre[y])-1;
}
int main()
{
scanf("%d",&t);
for(int i=1;i<=maxn;++i) fa[i]=i,rank[i]=1,son[i]=i;
while(t--)
{
char c;
int x,y;
cin>>c;
scanf("%d%d",&x,&y);
if(c=='M') merge(x,y);
else printf("%d\n",ask(x,y));
}
return 0;
}
其实显然这样会超时。顺着找一遍son,相当于没有路径压缩的并查集,时间复杂度很高,这份代码交到洛谷上WA了3个点。
回到最初的想法。
虽然破坏了树结构,但其实我们照样能做。
合并时还是没有变,把排在后面的集合的根节点的pre+=前面集合的大小。在路径压缩时,pre[x]+=pre[fa[x]]。先递归更新pre[fa[x]]的值,再回来改变pre[x]。如果fa[x]指向的是根节点,由于根节点的pre值一定是0,所以pre[fa[x]]一定不会变化。如果fa[x]指向的不是根节点也就是说之前他还没有路径压缩,我们就继续递归下去,并改变pre。
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=30000+10;
int t;
int fa[maxn],rank[maxn],pre[maxn];
int find(int x)
{
if(fa[x]==x) return x;
int f=fa[x];
fa[x]=find(fa[x]);
pre[x]+=pre[f];
return fa[x];
}
void merge(int x,int y)//x合并到y上
{
int fx=find(x),fy=find(y);
fa[fx]=fy;
pre[fx]+=rank[fy];
rank[fy]+=rank[fx];
}
int ask(int x,int y)
{
int fx=find(x),fy=find(y);
if(fx!=fy) return -1;
return max(pre[y],pre[x])-min(pre[x],pre[y])-1;
}
int main()
{
scanf("%d",&t);
for(int i=1;i<=maxn;++i) fa[i]=i,rank[i]=1;
while(t--)
{
char c;
int x,y;
cin>>c;
scanf("%d%d",&x,&y);
if(c=='M') merge(x,y);
else printf("%d\n",ask(x,y));
}
return 0;
}