题意
有 (n) 种已知物质,现在手上有 (m) 种,每种无限多个。已知 (k) 种反应,每种可以将一些反应物变成一些生成物。求经过这些反应过后最多可以有多少种不同的物质。
( exttt{Data Range:}1leq mleq nleq 10^5,1leq kleq 10^5)
题解
由于考场上不会写各种暴力所以来练习一下如何写优雅的暴力。
首先注意到如果所有反应都是化合或分解的话就是建个图 DFS 一下就好了,但是有多变多的就不好做。
但是我们要有梦想。有一个非常暴力的方法是不断进行 (1sim n) 的所有反应,如果在一轮所有反应进行过后并不能使得物质种类数增加那么就认为我们得出了答案。
这个东西效率比较低下,于是考虑怎么优化。注意到如果已经进行了某个反应的话那么以后就再也不用进行了,因为再做还是只能得到那些生成物,相当于没用,所以一个反应最多进行一次。
不仅如此,这里还有第二个优化:考虑记录一下每个物质能参加哪些反应,这样当取出一个生成物的时候就能很快的知道这个物质可以参加哪些反应而不是 (O(k)) 去找。
这里还有一个优化,配合第二个优化能跑得很快,就是可以不需要直接记录每个反应需要哪些物质,而是记录要完成这个反应还需要多少种物质,因为有第二个优化所以取出生成物的时候可以直接减 (1),如果这个反应不需要物质的话那么就一定可以反应了。
最后,这 (k) 个反应的依赖顺序组成了一个 DAG,所以可以用拓扑排序的思路来更新这些反应,于是就做完了。
代码
#include<bits/stdc++.h>
using namespace std;
typedef int ll;
typedef long long int li;
const ll MAXN=2e5+51;
queue<ll>q;
vector<ll>re[MAXN],g[MAXN];
ll n,m,kk,x,top,res;
ll vis[MAXN],visr[MAXN],l[MAXN],r[MAXN],need[MAXN];
inline ll read()
{
register ll num=0,neg=1;
register char ch=getchar();
while(!isdigit(ch)&&ch!='-')
{
ch=getchar();
}
if(ch=='-')
{
neg=-1;
ch=getchar();
}
while(isdigit(ch))
{
num=(num<<3)+(num<<1)+(ch-'0');
ch=getchar();
}
return num*neg;
}
int main()
{
n=read(),m=read();
for(register int i=1;i<=m;i++)
{
vis[read()]=1;
}
kk=read();
for(register int i=1;i<=kk;i++)
{
l[i]=read(),r[i]=read();
for(register int j=1;j<=l[i];j++)
{
!vis[x=read()]?re[x].push_back(i),need[i]++:1;
}
for(register int j=1;j<=r[i];j++)
{
g[i].push_back(read());
}
!need[i]?q.push(i),visr[i]=1:1;
}
while(!q.empty())
{
top=q.front(),q.pop();
for(register int i:g[top])
{
if(vis[i])
{
continue;
}
vis[i]=1;
for(register int j:re[i])
{
!visr[j]&&!(--need[j])?q.push(j),visr[j]=1:1;
}
}
}
for(register int i=1;i<=n;i++)
{
res+=vis[i];
}
printf("%d
",res);
for(register int i=1;i<=n;i++)
{
vis[i]?printf("%d ",i):1;
}
}