题目分析:
这道题其实一点都不难,也十分容易想到应该要用的数据结构:LCA。但是在我讲这道题的正解之前,我先讲一下我最开始仅用并查集的错误做法:
线内为错误做法:
我们将每个瓶子看作一个集合,里面的物质为其中的元素,这样我们就可以用并查集的Fa[]来处理集合。
Fa[I]表示I号物质在第Fa[I]号瓶子中
所以我们每次将一个瓶子倒进另一个瓶子的时候就变为了合并这两个集合:
for(int I=1;I<=M;I++){
scanf("%lld%lld",&X,&Y);
LL Fx=Find(X),Fy=Find(Y);
if(Fx!=Fy){
Fa[Fx]=Fy;
}
}
接下来我们得到按先后顺序得到的反应次序,对于每两个元素,先判断是否处于同一个集合,如果是的话就修改并记录答案:
for(int I=1;I<=K;I++){
scanf("%lld%lld",&X,&Y);
LL Fx=Find(X),Fy=Find(Y);
if(Fx==Fy){
LL Cost=min(G[X],G[Y]);
Ans+=Cost;
G[X]-=Cost;
G[Y]-=Cost;
}
}
最后输出答案:
printf("%lld",Ans<<1);
这样做似乎没有问题(样例水)?但是实际上有很大的问题,题目当中说了给出的可反应的两个物质是按照先后反应顺序的,而此处我们单用并查集是解决不了的。
以上就是我最开始的错误做法,但是这也给了我一个提示:我们要处理出反应的一个顺序。
回到LCA,我们考虑将每个物质(每种药)当作树的叶子节点,如果操作X倒入Y,我们就建立一个新点,将X,Y与新点连边,并且将Y的位置记录为新点。这样操作的好处在于,我们最后得到的根节点必然是最后才反应的,即:
对于两个反应I,J,如果树上深度Deep[I]>Deep[J],那么反应I比反应J先反应;
如果Deep[I]==Deep[J],那么就按照输入的反应顺序比较反应的先后;
这样处理完之后,我们只需要像并查集那样类似处理答案即可。但是要注意,由于题目当中的范围限制,所以我们得到的不一定是一棵树,有可能是一个森林。
关键代码:
scanf("%lld%lld%lld",&N,&M,&K);
for(I=1;I<=N;I++){
scanf("%lld",&G[I]);Loc[I]=I;
}
for(I=1;I<=M;I++){
int X=Read(),Y=Read();
Insert(Loc[X],N+I);Insert(N+I,Loc[X]);
Insert(Loc[Y],N+I);Insert(N+I,Loc[Y]);Loc[Y]=N+I;
}
for(I=M+N;I;I--){
if(!Fa[I][0]){
DFS(I);
}
}
for(I=1;I<=K;I++){
int X=Read(),Y=Read();
int Lca=LCA(X,Y);
if(Lca!=0){
Q[++Tot]=(Node){X,Y,Deep[Lca],Tot};
}
}
sort(Q+1,Q+1+Tot,Cmp);
for(I=1;I<=Tot;I++){
int X=Q[I].X,Y=Q[I].Y;
int Cost=min(G[X],G[Y]);
G[X]-=Cost,G[Y]-=Cost;Ans+=Cost;
}
printf("%lld",Ans<<1);