当前位置: 代码迷 >> 综合 >> NOIP2010 关押罪犯 & NOI2001 食物链——种类并查集
  详细解决方案

NOIP2010 关押罪犯 & NOI2001 食物链——种类并查集

热度:45   发布时间:2024-02-19 21:52:04.0

NOIP2010 关押罪犯 & NOI2001 食物链——种类并查集
(这两道题目是十分典型的种类并查集

种类并查集知识见引用:
算法学习笔记(7):种类并查集

NOIP2010 关押罪犯
题目只给出了两个罪犯间的影响值,显然这道题需要用到贪心,也可使用二分图的相关知识,这里只使用种类并查集

根据题目我们只知道哪两个人是不适合呆在同一个监狱的,但是并不知道哪两个人适合呆在同一间监狱,这就给了我们并查集一定的处理难度。此时种类并查集就有其用武之地了。

根据题意可知,有A,B两间监狱,因此我们可以开一个大小为2n的并查集,1~n代表A监狱,n+1~2n代表B监狱。
并根据贪心的思想,先处理影响大的,将二者尽量分在两间不同的监狱,=>连接x与y+n 和 连接y与x+n(两种情况:1.x在A,y在B 2.x在B,y在A)

显然,一开始连接时,二者所在集合一定不同,若发现新处理的二者在的集合相同则说明此时这两个人一定无法安排在两间不同监狱(根据贪心可知此时的解即为最优解)

代码实现:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
#define maxn 100100
#define ll long long
#define clear(a) memset(a,0,sizeof a)int n,m,ans;
int fa[maxn];
struct edge{
    int x,y,s;
}e[maxn<<1];bool cmp(edge a,edge b){
    return a.s>b.s;
}int findx(int x){
    if(x==fa[x])return x;else return fa[x]=findx(fa[x]);
}void mergex(int x,int y){
    int fx=findx(x);int fy=findx(y);if(fx!=fy)fa[fx]=fy;
}int main(){
    scanf("%d%d",&n,&m);for(int i=1;i<=m;i++)scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].s);sort(e+1,e+m+1,cmp);for(int i=1;i<=2*n;i++)fa[i]=i;for(int i=1;i<=m;i++){
    int fx=findx(e[i].x);int fy=findx(e[i].y);if(fx!=fy){
    //将两者分在两间不同监狱中,且有两种情况mergex(e[i].x,e[i].y+n);mergex(e[i].y,e[i].x+n);}else{
    //根据贪心策略可知此时的影响一定是最小的ans=e[i].s;break;}}printf("%d\n",ans);return 0;
}

NOI2001 食物链
这道题目比关押罪犯复杂,由题目可知存在三个组,A,B,C;B被A吃,C被B吃,A被C吃
与上题类似,我们也可以开一个三倍的并查集,1~n代表A物种,n+1~2n代表B物种,2n+1~3*n代表C物种;用这样的并查集维护关系;

当题目给出了x,y是同一物种时,若此为真话(后面讨论如何判断一句话的真假性),此时有三种情况,x,y均为A物种/B物种/C物种。故我们此时需要将(x,y) (x+n,y+n) (x+2n,y+2n)三种情况均合并。

若给出2 x y 即y被x吃,假设该句话为真话,此时也有三种情况,x为A且y为B / x为B且y为C / x为C且y为A ,因此这时我们需要将(x,y+n) (x+n,y+2n) (x+3n,y)三种情况合并。

接下来是如何判断真假:当操作为1时,说x,y是同类,由于食物链是环形的,我们仅需证明x不吃y和y不吃x即可,这时候也有分组的三种情况,但是我们发现在上面连边过程中我们每次都一次连3组,也就是我们只需证明一组,这三组的正确性也均能得到保证。这里我们可以只假设x,y只属于A,B两组,故只需满足(x,y+n) 不在同一集合中并且(x+n,y)也不在同一集合中

同理,在操作为2时判断真假时:只需证明二者不是同一物种(x,y二者不在同一集合中)和不是y吃x(即(x+n,y)不在同一集合中)

最后注意判断id大于n的情况和自己吃自己的情况

代码实现:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
#define ll long long
#define maxn 50010
#define clear(a) memset(a,0,sizeof a)int n,k,ans;
int fa[maxn*3];int findx(int x){
    if(x==fa[x])return x;else return fa[x]=findx(fa[x]);
}void mergex(int x,int y){
    int fx=findx(x);int fy=findx(y);if(fx==fy)return;fa[fx]=fy;
}int main(){
    scanf("%d%d",&n,&k);for(int i=1;i<=3*n;i++)fa[i]=i;for(int i=1;i<=k;i++){
    int id,x,y;scanf("%d%d%d",&id,&x,&y);if(x>n||y>n||(id==2&&x==y)){
    ans++;continue;}if(id==1){
    if(findx(x)==findx(y+n)||findx(x+n)==findx(y))ans++;//三组情况仅需判断一组即可else{
    mergex(x,y);mergex(x+n,y+n);mergex(x+2*n,y+2*n);//属于同一集合的三种情况}}else{
    if(findx(x)==findx(y)||findx(y)==findx(x+n))ans++;else{
    mergex(x,y+n);mergex(x+n,y+2*n);mergex(x+2*n,y);//x吃y也有三种情况}}}printf("%d\n",ans);return 0;
}