HDU-4829 Information(带权可删并查集+虚点)

题意

N N 个坦克,完成以下 M 种操作:
1. 1. A A B X X Y
表示 A A 坦克移动到了与 B坦克的相对位置是 (X,Y) ( X , Y ) 的地方,即 xA=xB+X,yA=yB+Y x A = x B + X , y A = y B + Y
2. 2. A A X Y Y
表示 A 坦克移动到了绝对位置是 (X,Y) ( X , Y ) 的地方,即 xA=X,yA=Y x A = X , y A = Y
3. 3. A A B X X Y
表示发现了 A A 坦克与 B 坦克的相对位置是 (X,Y) ( X , Y ) ,即 xA=xB+X,yA=yB+Y x A = x B + X , y A = y B + Y ,若无矛盾则执行,否则输出“REJECT”。
4. 4. A A X Y Y
表示发现了 A 坦克的绝对位置是 (X,Y) ( X , Y ) ,即 xA=X,yA=Y x A = X , y A = Y ,若无矛盾则执行,否则输出“REJECT”。
我们需要你对于如下两种询问及时做出回应:
5. 5. A A B
表示询问A坦克与B坦克的相对位置是多少,即分别求出 xAxB x A − x B 以及 yAyB y A − y B ,若不能求出,则输出“UNKNOWN”。
6. 6. A A
表示询问A坦克的绝对位置是多少,即求出 xA yA y A ,若不能求出,则输出“UNKNOWN”。
1n,m100000 1 ≤ n , m ≤ 100000

思路

首先,相对位置的关系是可以传递的,这点用带权并查集就可以实现, A A B 的边权意味着 B B 关于 A 的相对位置,边权是一个数对,用结构体重载加减等号即可。
而挪位置这个操作有点麻烦,由于挪动一个点的位置可能会导致连向这个点的点相对根的位置也发生改变,所以,我们要将并查集“可删化”。比较方便的操作是将并查集中对应的点弃置(只是这个点不再被查询,在连向它的点的松弛过程中仍有用),在并查集中另找一个位置代表这个点,已形成新的映射,这样,原先的点对并查集中的点询问或修改时,都要通过一个 id i d 数组映射位置,这样一来,并查集数组和 id i d 数组大小是 n+m n + m 级别的。发现时,只用对原来有边权的点检查是否边权和发现的不同即可。
最后是这个“绝对位置”,图论中特殊考虑的东西往往可以建一个“虚点”解决。像这道题,由于绝对位置需要查询修改,我们将其均转化为并查集擅长的相对位置来做,建立一个 0 0 号节点,一开始根是自己。对于绝对位置明确的点向 0 节点连边,查询某个点的绝对位置就相当于查询它和 0 0 <script type="math/tex" id="MathJax-Element-62">0</script> 节点的相对位置了。

代码

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define FOR(i,x,y) for(int i=(x);i<=(y);i++)
#define DOR(i,x,y) for(int i=(x);i>=(y);i--)
#define N 200003 
typedef long long LL;
using namespace std;
struct coor
{
    int x,y;
    coor operator -(){return (coor){-x,-y};}
    coor operator +(const coor &_)const{return (coor){x+_.x,y+_.y};}
    coor operator -(const coor &_)const{return (coor){x-_.x,y-_.y};}
    bool operator !=(const coor &_)const{return x!=_.x||y!=_.y;}
};
int fa[N],id[N];
coor dis[N];
int n,cnt;

int getfa(int k)
{
    if(k==fa[k])return k;
    int t=getfa(fa[k]);
    dis[k]=dis[k]+dis[fa[k]];
    return fa[k]=t;
}
void mer(int x,int y,coor val)
{
    x=id[x],y=id[y];
    int fx=getfa(x),fy=getfa(y);
    if(fx==fy)return;
    fa[fx]=fy;
    dis[fx]=-dis[x]+val+dis[y];
}

void set_new(int a)
{
    id[a]=++cnt;
    fa[id[a]]=id[a];
    dis[id[a]]=(coor){0,0};
}
bool judge(int x,int y){return getfa(id[x])==getfa(id[y]);}
coor pos(int x,int y){getfa(id[x]),getfa(id[y]);return dis[id[x]]-dis[id[y]];}

int main()
{
    int T;
    scanf("%d",&T);
    FOR(Ti,1,T)
    {
        printf("Case #%d:\n",Ti);
        scanf("%d",&n);
        cnt=0;
        fa[0]=id[0]=0,dis[0]=(coor){0,0};
        FOR(i,1,n)set_new(i);
        FOR(i,1,n)
        {
            int op,a,b=0,x,y;
            scanf("%d",&op);
            if(op<=4)
            {
                if(op&1)scanf("%d%d%d%d",&a,&b,&x,&y);
                else scanf("%d%d%d",&a,&x,&y);
                if(op<=2)set_new(a);
                else if(judge(a,b)&&pos(b,a)!=(coor){x,y}){printf("REJECT\n");continue;}
                mer(a,b,(coor){-x,-y});
            }
            else
            {
                if(op==5)scanf("%d%d",&a,&b);
                else scanf("%d",&a);
                if(judge(a,b))
                {
                    coor tmp=pos(b,a);
                    printf("%d %d\n",tmp.x,tmp.y);
                }else printf("UNKNOWN\n");
            }
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值