洛谷 P1196 银河英雄传说

题目描述

公元五八○一年,地球居民迁移至金牛座α第二行星,在那里发表银河联邦

创立宣言,同年改元为宇宙历元年,并开始向银河系深处拓展。

宇宙历七九九年,银河系的两大军事集*在巴米利恩星域爆发战争。泰山压

顶集**宇宙舰队司令莱因哈特率领十万余艘战舰出征,气吞山河集*点名将杨

威利组织麾下三万艘战舰迎敌。

杨威利擅长排兵布阵,巧妙运用各种战术屡次以少胜多,难免恣生骄气。在

这次决战中,他将巴米利恩星域战场划分成30000列,每列依次编号为1, 2, …,

30000。之后,他把自己的战舰也依次编号为1, 2, …, 30000,让第i号战舰处于

第i列(i = 1, 2, …, 30000),形成“一字长蛇阵”,诱敌深入。这是初始阵形。当

进犯之敌到达时,杨威利会多次发布合并指令,将大部分战舰集中在某几列上,

实施密集攻击。合并指令为M i j,含义为让第i号战舰所在的整个战舰队列,作

为一个整体(头在前尾在后)接至第j号战舰所在的战舰队列的尾部。显然战舰

队列是由处于同一列的一个或多个战舰组成的。合并指令的执行结果会使队列增

大。 然而,老谋深算的莱因哈特早已在战略上取得了主动。在交战中,他可以通

过庞大的情报网络随时监听杨威利的舰队调动指令。

在杨威利发布指令调动舰队的同时,莱因哈特为了及时了解当前杨威利的战

舰分布情况,也会发出一些询问指令:C i j。该指令意思是,询问电脑,杨威利

的第i号战舰与第j号战舰当前是否在同一列中,如果在同一列中,那么它们之

间布置有多少战舰。

作为一个资深的高级程序设计员,你被要求编写程序分析杨威利的指令,以

及回答莱因哈特的询问。

最终的决战已经展开,银河的历史又翻过了一页……

输入输出格式

输入格式:

 

输入文件galaxy.in的第一行有一个整数T(1<=T<=500,000),表示总共有T

条指令。

以下有T行,每行有一条指令。指令有两种格式:

  1. M i j :i和j是两个整数(1<=i , j<=30000),表示指令涉及的战舰编号。

该指令是莱因哈特窃听到的杨威利发布的舰队调动指令,并且保证第i号战

舰与第j号战舰不在同一列。

  1. C i j :i和j是两个整数(1<=i , j<=30000),表示指令涉及的战舰编号。

该指令是莱因哈特发布的询问指令。

 

输出格式:

 

输出文件为galaxy.out。你的程序应当依次对输入的每一条指令进行分析和

处理:

如果是杨威利发布的舰队调动指令,则表示舰队排列发生了变化,你的程序

要注意到这一点,但是不要输出任何信息;

如果是莱因哈特发布的询问指令,你的程序要输出一行,仅包含一个整数,

表示在同一列上,第i 号战舰与第j 号战舰之间布置的战舰数目。如果第i 号战

舰与第j号战舰当前不在同一列上,则输出-1。

 

输入输出样例

输入样例#1:

4
M 2 3
C 1 2
M 2 4
C 4 2

输出样例#1:

-1
1

 

解题思路

不用说肯定用并查集,貌似就是不进行路径压缩的无脑模拟?不对,500000个操作,30000艘船,不超时才怪!
 
那怎么办呢?一路径压缩战舰顺序就被改变,怎么才能在路径压缩的同时随时得知同一舰队中两艘战舰的位置?
 
输入是合并与询问两艘战舰之间的“距离”,遇到问题是路径压缩后“距离”(间隔战舰数量)变了,询问不好维护了,但不压缩太慢了,那我们不就可以再开一个数组,存下需要的“距离”了吗?这个数组存的距离就是在路径压缩时变化了的那个:第i艘到第fa[i]艘之间的战舰数量,数组名就命名成front吧,因为路径压缩全部完成,即同一舰队中所有元素的fa[i]都等于这个舰队第一艘战舰时,front[i]=第i艘战舰前方有多少战舰(这么搞就像前缀和,路径压缩时可以一层一层地边压缩边修正下去)。

易知在还未进行路径压缩时对于同一舰队非第一艘战舰,front[i]=1,第一艘战舰front[i]=0(一个舰队的第一艘,不是编号为一的那艘),路径压缩首先不断向fa[i]走fa[i]=find(fa[i]),走到队首,fa[i]==i,front[i]=0不变,返回队首的编号,回溯至递归上一层,把队首的编号那么第二艘的front增加0就是1,为何是增加呢?因为前面说过完成路径压缩即find函数跑完后front[i]的值就是编号i的战舰前方有几艘战舰,路径压缩前则是到fa[i]的距离,一路径压缩,就相当于fa[i]直接越过front[fa[i]]艘战舰,从i的前一艘指向队首,fa[i]前进那么多,front[i]自然也要增加那么多,修改之后继续回溯,同理第三层front[i]+=front[fa[i]]……路径压缩完成。

合并时怎么办呢,定义合并函数uni(x,y)表示将x所在那列移到y所在那列后面(千万别搞反了),那么我们就要先找到两列的队首(依然用x、y存),像普通并查集那样fa[x]=y,然后维护front数组,这时遇到问题啦——front[y]要加多少呢?显然是x那列的战舰数,难道还要循环一遍统计一下吗?那太慢了,存下来吧,于是num[i]表示编号i这列的战舰总数(i是队首,不然每合并一次要修改的太多了,查询时num[i]时先find(i)找到队首吧),front[y]+=num[x],num[x]+=num[y],合并完成。

还有一个问题就是询问。对于一组询问ask(x,y),先找到他们的队首fx=find(x);fy=find(y);(顺便把路径压缩进行完全了,不用担心front[i]被重复增加了,路径压缩完全时front[fa[i]]==0,因为fa[i]就是队首呀),然后判断fx!=fy就输出-1,否则就输出abs(front[x]-front[y])-1(到队首的距离之差减一就是他们间隔距离)。

最后就只剩一个问题啦——上代码
#include<stdio.h>
#include<math.h>//洛谷的港记告诉我这里没有abs(),是我用的不对吗…………
int fa[30010],num[30010]={0},front[30010]={0};
inline int abs(int a)
{
    return a>0?a:(-a);
}
int find(int x)
{
    if(x==fa[x]) return x;
    int t=find(fa[x]);
    front[x]+=front[fa[x]];
    fa[x]=t;
    return fa[x];
}
void uni(int x,int y)
{
    x=find(x);y=find(y);
    fa[x]=y;
    front[x]+=num[y];
    num[y]+=num[x];
}
void ask(int x,int y)
{
     int fx=find(x);
     int fy=find(y);
     if(fx==fy)
         printf("%d
",abs(front[x]-front[y])-1);
     else printf("-1
");
}

int main()
{
    //freopen("test.in","r",stdin);
    int q;
    char c;
    int x,y;
    scanf("%d
",&q);
    for(int i=0;i<30010;i++)
    {
        fa[i]=i;
        num[i]=1;
    }
    for(int i=0;i<q;i++)
    {
        scanf("%c %d %d
",&c,&x,&y);
        if(c=='M') uni(x,y);
        else ask(x,y);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/wawcac-blog/p/6821770.html