zoukankan      html  css  js  c++  java
  • 洛谷 P2024 食物链

    P2024 食物链

    法一:种类并查集

    #include<cstdio>
    int fa[250000];
    int n,k,ans;
    //fa[i]表示与i同类的集合,fa[i+n]表示i吃的集合,fa[i+2n]表示吃i的集合
    int find(int x)
    {
    	if(fa[x]!=x)	fa[x]=find(fa[x]);
    	return fa[x];
    }
    //int union1(int x,int y)
    //{
    //	x=find(x);
    //	y=find(y);
    //	fa[x]=y;
    //}//直接写进主程序 
    int main()
    {
    	int i,a,b,c,f1,f2,f3,f4;
    	scanf("%d%d",&n,&k);
    	for(i=1;i<=3*n;i++)
    		fa[i]=i;
    	for(i=1;i<=k;i++)
    	{
    		scanf("%d%d%d",&a,&b,&c);
    		if(b>n||c>n||(a==2&&b==c))
    			ans++;
    		else
    		{
    			if(a==1)
    			{
    				f1=find(b);
    				f2=find(c);
    				f3=find(c+n);
    				f4=find(c+2*n);
    				if(f1==f3||f1==f4)//如果已知b被c吃或b吃c则为假话
    					ans++;
    				else
    					if(f1!=f2)
    					{
    						fa[f1]=f2;//与b同类的集合和与c同类的集合合并
    						fa[find(b+n)]=f3;//b吃的集合和c吃的集合合并
    						fa[find(b+2*n)]=f4;//吃b的集合和吃c的集合合并
    					}
    			}
    			if(a==2)
    			{
    				f1=find(b);
    				f2=find(c);
    				f3=find(c+n);
    				f4=find(c+2*n);
    				if(f1==f2||f1==f3)//如果已知b和c同类或b被c吃则为假话
    					ans++;
    				else
    					if(f1!=f4)
    					{
    						fa[find(b+n)]=f2;//b吃的集合和与c同类的集合合并
    						fa[find(c+2*n)]=f1;//吃c的集合和与b同类的集合合并
    						fa[find(b+2*n)]=f3;//吃b的集合和c吃的集合合并 
    						//!!!上面这种非常容易忘,当心了 
    					} 
    			}
    		}
    	}
    	printf("%d",ans); 
    	return 0;
    }

    法二:加权并查集

    加权并查集的做法并非将同类放入同一个并查集,而是将所有有关系的动物都放入同一个并查集。这一点跟上面的做法不同。
    r[i]表示i结点与父亲结点的关系,r[i]=0 表示father[i]与i同类;1表示father[i]吃i;2表示i吃father[i]。
    在这道题中,处理合并与路径压缩时的权值变化的计算方法基本上都由枚举、找规律得出。事实上,即使没有规律,或许也可以通过直接写大量if判断语句来处理权值变化。处理权值变化的方法需要具体情况具体分析。

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    int father[60000];
    int r[60000];
    int n,k;
    int find(int x)
    {
        if(x!=father[x])
        {
            int xx=father[x];
            father[x]=find(father[x]);
            r[x]=(r[x]+r[xx])%3;//见“解释4”
        }
        return father[x];
    }
    int ans;
    int main()
    {
        int a,i,b,c,fb,fc;
        scanf("%d%d",&n,&k);
        for(i=1;i<=n;i++)
            father[i]=i;
        for(i=1;i<=k;i++)
        {
            scanf("%d%d%d",&a,&b,&c);
            if(b>n||c>n||(b==c&&a==2))//第二和第三条判定假话的方法
                ans++;
            else
            {
                fb=find(b);
                fc=find(c);
                if(a==1)
                {
                    if(fb==fc)//b和c在相同集合中,即能确定b和c有关系
                    {
                        if(r[b]!=r[c])//在find中已经完成路径压缩,因此r[b]、r[c]表示它们与同一个结点的关系
                            ans++;//如果它们与该结点关系相同,显然b、c同类,不矛盾,反之则矛盾,答案加1
                    }
                    else//如果不能确定b、c有关系
                    {
                        father[fc]=fb;//合并b、c所在集合
                        r[fc]=(r[b]-r[c]+3)%3;//这个不好解释,见下方“解释1”
                    }
                }
                else//如果输入是b吃c
                {
                    if(fb==fc)//如果可以确定b与c的关系
                    {
                        if((r[b]+1)%3!=r[c])//如果b不是吃c的(原因见“解释2”)
                            ans++;//答案加1
                    }
                    else
                    {
                        father[fc]=fb;//如果不能确定b与c关系,则合并b和c所在集合
                        r[fc]=(r[b]-r[c]+4)%3;//见“解释3”
                    }
                }
            }
        }
        printf("%d",ans);
        return 0;
    }

    解释1:
    可以知道此时b和c是同类,并且b和c所在的集合都已经完成了路径压缩,也就是b和c此时的父结点分别就是fb和fc。直接枚举r[b]、r[c]值,推出对应r[fc]值,找规律即可。
    举例:r[b]=1,r[c]=2时,fb吃b,c吃fc,b和c同类,可得fc吃fb,即r[fc]=2。
    很容易发现r[fc]=(r[b]-r[c]+3)%3。
    解释2:
    b和c所在的集合都已经完成了路径压缩,且fb==fc,也就是b和c此时的父结点为同一个即fb。枚举r[b]值推出r[c]可找出规律。
    举例:r[b]=1,那么b被fb吃,而如果b吃c,显然c吃fb,因此r[c]=2。
    很容易发现当且仅当(r[b]+1)%3==r[c]时b是吃c的。
    解释3:
    可以知道此时b吃c,并且b和c所在的集合都已经完成了路径压缩,也就是b和c此时的父结点分别就是fb和fc。仍然是枚举r[b]、r[c]值推倒r[fc]值找规律。
    举例:r[b]=1,r[c]=0,那么b被fb吃,而b吃c,因此c吃fb,而c与fc同类,因此fc吃fb,因此r[fc]=2。
    很容易发现r[fc]=(r[b]-r[c]+4)%3。
    解释4:
    如果某点与其父结点关系为a,其父结点与根结点关系为b,那么可以知道该点与根结点关系为(a+b)%3。
    举例:某点被父结点吃,即r[点]=1,父结点吃根结点,即r[father[点]]=2,那么显然该点与根结点同种类,即路径压缩后r[点]=(1+2)%3=0。
    (这个不好证也没必要证,多列几组试一下就知道了)

    推荐优秀讲解:

    食物链
    食物链

  • 相关阅读:
    C#的dll被其他程序调用时,获取此dll正确的物理路径
    用鼠标右键选择DataGridView单元格或行
    当心回车符破坏你的JSON数据
    WinForm中当TextBox重新获得焦点时输入法失效问题
    django-rest-framework登陆认证
    django-celery定时任务以及异步任务and服务器部署并且运行全部过程
    linux安装mysqlclient报错
    Dajngo的CBV和FBV
    五分钟看懂Celery定时任务
    Nginx+uWsgi生产部署Django
  • 原文地址:https://www.cnblogs.com/hehe54321/p/8470450.html
Copyright © 2011-2022 走看看