洛谷 P1456 Monkey King 题解

标签:左偏树&并查集

思路来源:

Q1:为什么用并查集?

A1:因为要判断两队猴子是否认识,在此处使用并查集可以便于维护两队猴子间的关系。

Q2:为什么用堆?

A2:因为每次对战都需要查找当前两队猴子中能力值最大的猴子,这里使用堆可以便于选出首领。

Q3:用什么样的堆?

A3:因为需要在两队猴子争执之后将两队猴子合并,并且不能破坏堆,所以要使用可并堆。

Q4:用什么可并堆?

A4:左偏树,斐波那契堆都是不错的可并堆,这里我使用的是左偏树。

具体做法:

-使用并查集维护两队猴子之间的关系,在争执前先特判两队猴子已经认识的情况。

-根据题目要求,用左偏树模拟合并过程即可。

参考代码

#include<iostream>
#include<cstdio>
using namespace std;

struct monkey {
    int lson;//用下标模拟地址 
    int rson;//用下标模拟地址 
    int power;//能力值 
    int dist;//节点到离它最近的外节点的距离(备注:外节点为叶节点或只有左(右)子树的节点) 
}m[100005];

int f[100005];

int ff(int ele) {//并查集-路径压缩 
    if (ele==f[ele]) {
        return ele;
    }
    return f[ele]=ff(f[ele]);
}

int merge(int ele1,int ele2) {//合并两个左偏树,并返回新书的根节点 
    if (ele1==0) {//如果有一棵待合并的树为空树,则合并结果为另一棵树 
        return ele2;
    }
    if (ele2==0) {//同上 
        return ele1;
    }
    if (m[ele1].power<m[ele2].power) {//保证第一棵树的能力值<第二颗树 
        swap(ele1,ele2);
    }
    m[ele1].rson=merge(m[ele1].rson,ele2);//将较小的树合并到较大树的右子树中 
    f[m[ele1].rson]=ele1;
    if (m[ele1].rson==0) {//更新根节点到离它最近的外节点的距离 
        m[ele1].dist=0;
    }
    else {
        m[ele1].dist=m[m[ele1].rson].dist+1;
    }
    if (m[m[ele1].lson].dist<m[m[ele1].rson].dist) {//根据左偏树的性质,当左侧的长度>右侧的长度时,要交换左右子树 
        swap(m[ele1].lson,m[ele1].rson);
    }
    return ele1;
}

int main() {
    int n,t,x,y,p,q,root,newx,newy;
    while (scanf("%d",&n)!=EOF) {
        for (register int i=1;i<=n;++i) {
            scanf("%d",&m[i].power);
        }
        for (register int i=1;i<=n;++i) {//在最初的时候,每只猴子独立形成一个群体 
            m[i].lson=0;
            m[i].rson=0;
            f[i]=i;
        }
        scanf("%d",&t);
        while (t--) {
            scanf("%d%d",&x,&y);
            p=ff(x);//找出各自的首领,这部分用并查集维护 
            q=ff(y);
            if (p==q) {//按题目要求,两只本来就认识的猴子不会发生争执 
                printf("-1\n");
                continue;
            }
            m[p].power/=2;//更新首领在争执后剩余的能力值 
            root=merge(m[p].lson,m[p].rson);//先合并原首领的左右子树 
            m[p].lson=0;//将首领孤立 
            m[p].rson=0;
            newx=merge(root,p);//让首领归队,这部分用左边数维护,以便选出首领 
            m[q].power/=2;//同上 
            root=merge(m[q].lson,m[q].rson);
            m[q].lson=0;
            m[q].rson=0;
            newy=merge(root,q);
            root=merge(newx,newy);//将争执后的两队猴子合并 
            f[newx]=root;//更新并查集 
            f[newy]=root;
            printf("%d\n",m[root].power);//输出新首领的能力值 
        }
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
过河卒是一个典型的动态规划问题。首先,我们将整个棋盘看作一个二维数组,数组的每个元素表示到达该位置的路径数目。然后,我们根据题目给出的条件,逐步更新数组中的元素,直到计算出到达目标位置的路径数目。 具体的解题思路如下: 1. 首先,我们可以将马的位置设置为0,表示无法经过该位置。 2. 然后,我们根据马的位置,更新数组中的元素。对于二维数组中的每个位置,我们根据左边和上边的位置来计算到达当前位置的路径数目。具体地,如果左边和上边的位置都可以经过,那么到达当前位置的路径数目就等于左边和上边位置的路径数目之和。如果左边或上边的位置无法经过,那么到达当前位置的路径数目就等于左边或上边位置的路径数目。 3. 最后,我们输出目标位置的路径数目。 下面是洛谷p1002过河卒题解的C++代码: ```cpp #include <bits/stdc++.h> using namespace std; int main() { long long a[21][21]; int x1, y1, x2, y2; cin >> x1 >> y1 >> x2 >> y2; // 初始化数组,马的位置设置为0 for(int i=0; i<=20; i++) { for(int k=0; k<=20; k++) { a[i][k] = 1; } } a[x2][y2] = 0; // 根据马的位置更新数组中的元素 if(x2 >= 2 && y2 >= 1) a[x2-2][y2-1] = 0; if(x2 >= 1 && y2 >= 2) a[x2-1][y2-2] = 0; if(x2 <= 18 && y2 >= 1) a[x2+2][y2-1] = 0; if(x2 <= 19 && y2 >= 2) a[x2+1][y2-2] = 0; if(x2 >= 2) a[x2-2][y2+1] = 0; if(x2 >= 1) a[x2-1][y2+2] = 0; if(y2 >= 1) a[x2+2][y2-1] = 0; if(y2 >= 2) a[x2+1][y2-2] = 0; // 动态规划计算路径数目 for(int i=1; i<=20; i++) { for(int k=1; k<=20; k++) { if(a[i][k] != 0) { a[i][k] = a[i-1][k] + a[i][k-1]; } } } // 输出目标位置的路径数目 cout << a[x1][y1] << endl; return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值