[PA2014] Fiolki

问题描述

化学家吉丽想要配置一种神奇的药水来拯救世界。

吉丽有n种不同的液体物质,和n个药瓶(均从1到n编号)。初始时,第i个瓶内装着g[i]克的第i种物质。吉丽需要执行一定的步骤来配置药水,第i个步骤是将第a[i]个瓶子内的所有液体倒入第b[i]个瓶子,此后第a[i]个瓶子不会再被用到。瓶子的容量可以视作是无限的。

吉丽知道某几对液体物质在一起时会发生反应产生沉淀,具体反应是1克c[i]物质和1克d[i]物质生成2克沉淀,一直进行直到某一反应物耗尽。生成的沉淀不会和任何物质反应。当有多于一对可以发生反应的物质在一起时,吉丽知道它们的反应顺序。每次倾倒完后,吉丽会等到反应结束后再执行下一步骤。

吉丽想知道配置过程中总共产生多少沉淀。

输入格式

第一行三个整数n,m,k(0<=m<n<=200000,0<=k<=500000),分别表示药瓶的个数(即物质的种数),操作步数,可以发生的反应数量。

第二行有n个整数g[1],g[2],…,g[n](1<=g[i]<=10^9),表示初始时每个瓶内物质的质量。

接下来m行,每行两个整数a[i],b[i] (1<=a[i],b[i]<=n,a[i]≠b[i]),表示第i个步骤。保证a[i]在以后的步骤中不再出现。

接下来k行,每行是一对可以发生反应的物质c[i],d[i] (1<=c[i],d[i]<=n,c[i]≠d[i]),按照反应的优先顺序给出。同一个反应不会重复出现。

输出格式

配置过程中总共产生多少沉淀

样例输入

3 2 1
2 3 4
1 2
3 2
2 3

样例输出

6

解析

初步想法是把每个操作的两个容器连一条边\(a->b\),那么所有的操作构成了一棵树。可以知道每个反应发生的地方一定在两个反应物的LCA上。但是,如果两个反应的LCA相同,会出现很多情况,我们很难确定这两个反应的顺序。所以,考虑改变图模型的构造方法。

反应发生的顺序由反应先后和操作的先后共同决定。我们可以把两个操作的点连向一个新的点,以后如果还需要使用这两个药瓶中的一个,就用这个新点代替。这样,用反应的LCA的深度和先后就可以确定反应顺序。然后模拟即可。

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#define int long long
#define N 500002
#define M 1000002
using namespace std;
struct event{
	int a,b,lca,id;
}c[M];
int head[N],ver[N*2],nxt[N*2],l;
int n,m,k,i,cnt,g[N],d[N],dep[N],fa[N],son[N],size[N],top[N],f[N];
int read()
{
	char c=getchar();
	int w=0;
	while(c<'0'||c>'9') c=getchar();
	while(c<='9'&&c>='0'){
		w=w*10+c-'0';
		c=getchar();
	}
	return w;
}
void insert(int x,int y)
{
	l++;
	ver[l]=y;
	nxt[l]=head[x];
	head[x]=l;
}
void dfs1(int x,int pre)
{
	fa[x]=pre;
	dep[x]=dep[pre]+1;
	size[x]=1;
	for(int i=head[x];i;i=nxt[i]){
		int y=ver[i];
		dfs1(y,x);
		size[x]+=size[y];
		if(size[y]>size[son[x]]) son[x]=y;
	}
}
void dfs2(int x,int t)
{
	top[x]=t;
	if(son[x]) dfs2(son[x],t);
	for(int i=head[x];i;i=nxt[i]){
		int y=ver[i];
		if(y!=fa[x]&&y!=son[x]) dfs2(y,y);
	}
}
int LCA(int u,int v)
{
	while(top[u]!=top[v]){
		if(dep[top[v]]>dep[top[u]]) swap(u,v);
		u=fa[top[u]];
	}
	if(dep[u]>dep[v]) swap(u,v);
	return u;
}
int my_comp(const event &x,const event &y)
{
	if(dep[x.lca]!=dep[y.lca]) return dep[x.lca]>dep[y.lca];
	return x.id<y.id;
}
int find(int x)
{
	if(x!=f[x]) f[x]=find(f[x]);
	return f[x];
}
signed main()
{
	n=read();m=read();k=read();
	for(i=1;i<=n+m;i++) f[i]=i;
	for(i=1;i<=n;i++) g[i]=read();
	for(i=1;i<=m;i++){
		int a=read(),b=read(),f1=find(a),f2=find(b);
		n++;d[f1]++;d[f2]++;
		insert(n,f1);
		insert(n,f2);
		f[f1]=f[f2]=n;
	}
	for(i=1;i<=n;i++){
		if(d[i]==0) dfs1(i,0),dfs2(i,i);
	}
	for(i=1;i<=k;i++){
		int x=read(),y=read(),z=LCA(x,y);
		if(z==0) continue;
		cnt++;
		c[cnt].a=x,c[cnt].b=y;
		c[cnt].lca=z,c[cnt].id=i;
	}
	sort(c+1,c+cnt+1,my_comp);
	int ans=0;
	for(i=1;i<=cnt;i++){
		int tmp=min(g[c[i].a],g[c[i].b]);
		g[c[i].a]-=tmp;g[c[i].b]-=tmp;
		ans+=tmp*2;
	}
	printf("%lld\n",ans);
	return 0;
}

Tips

在做的时候主要有两个问题:

  1. 建树的时候没有将两个点合并得到的新点加进来,导致需要讨论很多情况。以后发现这个问题时要及时转换模型的构造,免得浪费时间。
  2. 排序时没有注意深度相同时也需要按照反应先后顺序排序而是单纯地当两个反应的LCA不同时按先后排序。
posted @ 2019-11-07 12:51  CJlzf  阅读(114)  评论(0编辑  收藏  举报