洛谷P2024 [NOI2001]食物链
题目大意:
森林里有三类动物满足A吃B,B吃C,C吃A的关系,现在有n个动物(编号从1到n),k条描述,判断这些描述中假话的个数。(1 ≤ N ≤ 5 ∗ 10^4,1 ≤ K ≤ 10^5)
其中,描述有两类:"1 x y"表示xy同类;"2 x y"表示x吃y。
假话有三类:x或y超过n;x吃x;与之前的描述矛盾。
思路:
三类动物会形成这样的关系,将有这些关系的边都设为1;对于C到A的关系,需要先到B再到A,把路上的关系相加就变成了2(注意到时(-1)%3的结果),我们就把这样的关系设为2;若是同类本来不会有边,为了好表示关系,可以加一条边权为0的边。这样,计算一只动物x到另一只动物y的关系,可以从x沿已知的边一直走到y,再加起来沿路的权值,由于可能会转好几个圈,所以还要%3,这种算法的核心大概就是上述的过程。
现在的问题就是一个一个走,太慢。由于同类之间也有边,所以整个森林的动物最终是相互连着的一个集合,那么可以借助并查集路径压缩的思想,每个点只记录自己与根的关系r[x],求x到y的关系直接(r[x]-r[y]+3)%3就好。
判断过程大致如下:
x,y是否在一个集合;
若不在一个集合,这就是一组新的关系,一定不会产生矛盾,所以合并他们;
若在一个集合,就求出二者的关系,在看与题中给的是否一致。
路径压缩:
注意既要更新r[x],又要更新f[x]; 所以要存一下,t=f[x];f[x]=find(f[x]);r[x]=r[x]+r[t];(左边的图)
合并:
合并的情况是右边的图,目前已知ra,要更新r[f[x]]为t,看图还是比较清晰的,t=r[y]+ra-r[x]。
下面是代码。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 5 using namespace std; 6 7 int f[50005],n,m,r[50005]; 8 9 int find(int x) 10 { 11 int t=f[x]; 12 if(f[x]!=x) 13 { 14 f[x]=find(f[x]); 15 r[x]=(r[t]+r[x])%3; 16 } 17 return f[x]; 18 } 19 20 bool judge(int x,int y) 21 { 22 int xx=find(x); 23 int yy=find(y); 24 if(xx==yy)return false; 25 else return true; 26 } 27 28 void uni(int x,int y,int ra) 29 { 30 int yy=find(y); 31 int xx=find(x); 32 f[xx]=yy; 33 r[xx]=(ra+r[y]-r[x]+3)%3; 34 return ; 35 } 36 37 int main() 38 { 39 scanf("%d%d",&n,&m); 40 int ans=0; 41 for(int j=1;j<=n;j++)f[j]=j; 42 for(int i=1;i<=m;i++) 43 { 44 int d,a,b; 45 scanf("%d%d%d",&d,&a,&b); 46 if(a>n||b>n) 47 { 48 ans++; 49 continue; 50 } 51 if(d==2&&a==b) 52 { 53 ans++; 54 continue; 55 } 56 if(judge(a,b))uni(b,a,d-1); 57 else 58 { 59 int bb=find(b);//用find来更新r[a]和r[b] 60 int aa=find(a); 61 if((r[b]-r[a]+3)%3!=d-1) 62 { 63 ans=ans+1; 64 } 65 } 66 } 67 printf("%d\n",ans); 68 return 0; 69 }