题解 P1196 【[NOI2002]银河英雄传说】

· · 题解

【参考资料】

三体智子的博客 - P1196 [NOI2002]银河英雄传说:https://www.luogu.org/blog/huangweixian123/p1196-noi2002-yin-he-ying-xiong-zhuan-shuo

【题目描述】

公元五八○一年,地球居民迁至金牛座α第二行星,在那里发表银河联邦创立宣言,同年改元为宇宙历元年,并开始向银河系深处拓展。

宇宙历七九九年,银河系的两大军事集团在巴米利恩星域爆发战争。泰山压顶集团派宇宙舰队司令莱因哈特率领十万余艘战舰出征,气吞山河集团点名将杨威利组织麾下三万艘战舰迎敌。

杨威利擅长排兵布阵,巧妙运用各种战术屡次以少胜多,难免恣生骄气。在这次决战中,他将巴米利恩星域战场划分成30000列,每列依次编号为1, 2, …,30000。之后,他把自己的战舰也依次编号为1, 2, …, 30000,让第i号战舰处于第i(i = 1, 2, …, 30000),形成“一字长蛇阵”,诱敌深入。这是初始阵形。当进犯之敌到达时,杨威利会多次发布合并指令,将大部分战舰集中在某几列上,实施密集攻击。合并指令为M i j,含义为第i号战舰所在的整个战舰队列,作为一个整体(头在前尾在后)接至第j号战舰所在的战舰队列的尾部。显然战舰队列是由处于同一列的一个或多个战舰组成的。合并指令的执行结果会使队列增

大。 然而,老谋深算的莱因哈特早已在战略上取得了主动。在交战中,他可以通过庞大的情报网络随时监听杨威利的舰队调动指令。

在杨威利发布指令调动舰队的同时,莱因哈特为了及时了解当前杨威利的战舰分布情况,也会发出一些询问指令:C i j。该指令意思是,询问电脑,杨威利的第i号战舰与第j号战舰当前是否在同一列中,如果在同一列中,那么它们之间布置有多少战舰。

作为一个资深的高级程序设计员,你被要求编写程序分析杨威利的指令,以及回答莱因哈特的询问。

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

【输入输出格式】

输入文件的第一行有一个整数T1 \leq T \leq 500,000),表示总共有T条指令。

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

  1. M i jij是两个整数(1 \leq i , j \leq 30000),表示指令涉及的战舰编号。该指令是莱因哈特窃听到的杨威利发布的舰队调动指令,并且保证第i号战舰与第j号战舰不在同一列。

  2. C i jij是两个整数(1 \leq i , j \leq 30000),表示指令涉及的战舰编号。该指令是莱因哈特发布的询问指令。

你的程序应当依次对输入的每一条指令进行分析和处理:

如果是杨威利发布的舰队调动指令,则表示舰队排列发生了变化,你的程序要注意到这一点,但是不要输出任何信息;

如果是莱因哈特发布的询问指令,你的程序要输出一行,仅包含一个整数,表示在同一列上,第i 号战舰与第j 号战舰之间布置的战舰数目。如果第i 号战舰与第j号战舰当前不在同一列上,则输出-1

【输入输出样例】

【样例说明】

战舰位置图:表格中阿拉伯数字表示战舰编号。

【题目大意】

30000条队列,总共n次操作,分M操作与C操作,M操作是把当前第i艘飞船所在的整条队列移到第j艘飞船艘所在的队列的最后面,C操作则是询问第i艘飞船与第j艘飞船是否在同一条队列,如果在则输出第i艘飞船与第j艘飞船之间的飞船数,不包括ij题目不保证ji后面,否则就输出-1

【题解】

我会并查集!

我们考虑用并查集解决这道题目。由于M操作较为简单,因此我们先对M操作入手。我们可以用一个数组size[]来存储每个队列当前所有的飞船的数量,在这里我用size[i]来表示第i条队列有多少艘飞船,并且用d[i]来表示i号飞船的前面有多少艘飞船所以当进行M操作时就将点x和点y合并,然后改一下两个数组的值即可~代码实现如下。

if(t[1]=='M')
{
    f[tx]=ty;
    d[tx]=size[ty];
    size[ty]+=size[tx];
    size[tx]=0;
}

接下来我们要处理的就是find()函数,一般我们写并查集的话函数是这样子的:

if(x==f[x])
{
    return x;
}
else
{
    return f[x]=find(f[x]);
}

但是在本题中,find()函数却是这样子的:

int find(int x)
{
    if(f[x]==x)
    {
        return x;
    }
    int t1=f[x];
    int t2=find(f[x]);
    f[x]=t2;
    d[x]+=d[t1];
    return t2;
}

在上面的一段代码中,t1表示的是点x原来的老大,t2表示的是点x现在的老大,因此将f[x]=t2(更新x点的祖宗)且将d[x]+=d[t1](增加点x的前面的船的个数)。

C操作也比较简单,如果两个点的老大不一样就输出-1,否则就输出|d[x]-d[y]|-1即可,不懂的可以自己去算一下。

下面上AC代码~

#include <cstdio>
#include <cstdlib>
int size[1000001],f[1000001],d[1000001];
int inf=30001;
int find(int x)
{
    if(f[x]==x)
    {
        return x;
    }
    int t1=f[x];
    int t2=find(f[x]);
    f[x]=t2;
    d[x]+=d[t1];
    return t2;
}
int main()
{
    int n=0;
    scanf("%d",&n);
    for(int i=1;i<=inf;i++)
    {
        f[i]=i;
        size[i]=1;
    }
    for(int i=1;i<=n;i++)
    {
        char t[5];
        int x=0,y=0;
        scanf("%s %d %d",t+1,&x,&y);
        int tx=find(x),ty=find(y);
        if(t[1]=='M')
        {
            f[tx]=ty;
            d[tx]=size[ty];
            size[ty]+=size[tx];
            size[tx]=0;
        }
        else if(t[1]=='C')
        {
            if(tx!=ty)
            {
                printf("-1\n");
            }
            else
            {
                printf("%d\n",abs(d[x]-d[y])-1);
            }
        }
    }
    return 0;
}