P2024 食物链
带权并查集。
听bin哥讲课,一开始很不明白(可能是我太弱了),但是后来自己想一想,也就明白带权并查集的实质了。
分析一下:
设A为父亲节点,B为A的子节点,那么对于以下的三种关系:
1.A吃B,边权为2。
2.A被B吃,边权为1。
3.A和B同种,边权为0。
那么对于w,就有关系式:边权=w-1
有个问题:就是w只有1或者2,那么对于边权为2的边,没办法用w表示,所以在连不能连的边的时候(至于为什么不能连的边要连,一会儿解释),就不能表示从左到右B吃C的情况,只能表示C吃B。所以对于下面的B和C,我们就按C是父亲节点来计算。(而且亿会儿也要假装把B连到C上,那自然C要假装作为B的父亲节点)
A
B C
那么有两个关键问题:
1.如果对于x和y已经相连,那么如何判断其关系是否正确。
2.对于没有相连的A,B两棵树(注意:A,B不是根节点),如何相连。
先解决第一个问题:
对于:
A
B C
其中假设A,B和A,C已经连了边,而B和C没有连边,现在的操作是w,B,C。
如果这个关系合法,那么就恒有AB=(AC+BC)%3 (别问我怎么证的:暴力枚举所有情况)
其中BC=w-1,方向由C——>B(其实这也决定了刚刚恒等式的方向)(至于为什么是C—>B刚刚证了).
对于方向问题其实没那么复杂,因为A->B,2和B->A,1是一样的。对于这里的BC的边权,认为是C到B的,也就说明对于w的描述,我们都要转化成C到B的(就是说下面那个图,是要把左下角的连到右上角的下面)。(反正规定是父亲节点到儿子节点,那么一会儿C也就是父亲节点(B连到C),那么也可证这条边应该按C—>B规定)
那么对于第二个问题:
考虑:
par[y]
par[x] |
| y
x
现在的操作是把x和y连上,我们当然不能连x和y。而应该连par[x],par[y],可是关键问题是并不知道par[x]和par[y]的边权大小,但是其实这个关系是可以求的(已知d[x],d[y],xy)(注意:xy是以y为父亲节点向x方向与x的连线构成的,描述y和x关系的边,xy=w-1)
有恒等式:
d[par[x]]=(d[y]-d[x]+xy)%3 (用到刚刚证的东西了)(其实平行四边形定则也可以,但是我并不会证,用就可以了)
那么为了防止为负,统一要+3,转换后:
d[par[x]]=(d[y]-d[x]+w+2)%3;par[par[x]]=par[y];
那就上代码啦:
#include<cstdio> using namespace std; #define maxn 50005 int d[maxn],par[maxn],n,k,ans; int find(int x) { if(x==par[x]) return x; int t=par[x]; par[x]=find(par[x]); d[x]=(d[t]+d[x])%3; return par[x]; } int main() { scanf("%d%d",&n,&k); for(int i=1; i<=n; i++) par[i]=i; for(int i=1; i<=k; i++) { int w,x,y; scanf("%d%d%d",&w,&x,&y); if(x > n || y > n || (w == 2 && x == y )) { ans++; continue; } if(find(x)==find(y)) { if(d[x]!=(d[y]+w-1)%3) { ans++; continue; } } else { d[par[x]]=(d[y]+w-d[x]+2)%3;//d[par[x]]=(d[y]+w-1-d[x]+3)%3 par[par[x]]=par[y]; } } printf("%d",ans); return 0; }

浙公网安备 33010602011771号