P2024 [NOI2001]食物链
-------------------------------
建三个并查集空间太大了,应该写加权并查集
-------------------------------
这道题的关系是个环,转过来转过去的。我们的权值就定义成这个点与父节点的关系(0=同类,1=吃
2=被吃)
------------------------------
这样做,我们就要在find时如果搞路径压缩,就要麻烦一点,因为我们要把他与父节点的关系更新成与
祖先节点的关系。
非常易证得,关系就是与父节点的关系+与祖先节点的关系的和mod3的值(因为递归的原因,这个祖先节点最多就是父节点的父节点)
int find(int x){ if(x!=f[x]) { int xx=f[x]; f[x]=find(f[x]); g[x]=(g[x]+g[xx])%3; } return f[x]; }
-----------------------------
看一下题目,首先把两种白痴情况排除:自己吃自己和>n
然后先考虑1的情况
+ 如果这俩祖先相同,就检查是不是同类,不是就ans++
+ 不同,就考虑合并祖先节点
> 计算祖先节点的关系可以倒着考虑,假如x,y是他们祖先的祖先且y是x的祖先,那么他们原来的祖先什么关系?
> 把y的祖先翻转,及变为3-g[y],然后就回归上面的线性求法了
再考虑下2
+ 在同一棵树上就仿照着1做,因为关系是吃要加1
+ 不同也是中这么搞,记得+1
而且合并的时候合并的是x,y的父亲,不是x,y。
if((x>n||y>n)||(x==y&&fl==2)){ ans++; continue; } if(fl==1){ if(find(x)==find(y)){ if(g[x]!=g[y]){ ans++; } } else{ g[f[x]]=(g[y]-g[x]+3)%3; f[f[x]]=f[y]; } } if(fl==2){ if(find(x)==find(y)){ if(g[x]!=(g[y]+1)%3){ ans++; } } else { g[f[x]]=(g[y]-g[x]+4)%3; f[f[x]]=f[y]; } }
-------------------------------------------------
完整代码
#include<iostream> #include<cstdio> using namespace std; int fl,x,y; int g[500001]; int f[500001]; int n,k; int cnt; int find(int x){ if(x!=f[x]) { int xx=f[x]; f[x]=find(f[x]); g[x]=(g[x]+g[xx])%3; } return f[x]; } int ans; int main(){ scanf("%d%d",&n,&k); for(int i=1;i<=n;++i){ f[i]=i; g[i]=0; } for(int i=1;i<=k;++i){ scanf("%d%d%d",&fl,&x,&y); if((x>n||y>n)||(x==y&&fl==2)){ ans++; continue; } if(fl==1){ if(find(x)==find(y)){ if(g[x]!=g[y]){ ans++; } } else{ g[f[x]]=(g[y]-g[x]+3)%3; f[f[x]]=f[y]; } } if(fl==2){ if(find(x)==find(y)){ if(g[x]!=(g[y]+1)%3){ ans++; } } else { g[f[x]]=(g[y]-g[x]+4)%3; f[f[x]]=f[y]; } } } cout<<ans<<endl; return 0; }