题解:P13118 [GCJ 2019 #2] Contransmutation
Little_duck_GGG · · 题解
题目意思
有
对于第
求金属
题目思路
首先考虑一种金属是否可以达到无限。
很容易想到,如果一个金属是无限的,那么它能生产的金属一定无限。
然后就要思考,如果一个金属无限,那么它满足什么条件。
很多人存在一个误区,就是认为假设两种金属能够相互生产,且其中一种金属非零,那么它们都能互相生产。~我一开始也是这么想的~。
但这样是错的。
给出一幅图就知道了。
这里假设
这里
因为不管两个点如何的传递,它们的值始终不变,且可以进行无限次操作。
那么既然能做无限次操作且值不变,换句话说,是不是第
这是不是启发我们可以缩点。
如果一种金属
那么对于一个被
这是一种情况。
我们再考虑一种情况。
此时
那这样无限的情况就考虑完了。
注意最后要跑一遍拓扑,因为如果一个点是无限,它所指向的点也是无限的。
那么接着考虑如果不是无限怎么处理。
那这种情况应该是不难处理的,我们跑一遍拓扑排序就可以了。
但其实做到这里还没完。
你得考虑金属初始值为零的情况。
假设三个点都在一个连通块内,且此时一个点指向另外两点,但三个点的初始权值都为零,那么这样肯定不是无限。
不过如果你以为这道题就做完了,那你又错了,我再给一个图。
这幅图中,
但如果按照我们上面的条件判断,那么我们应该会判断
但是这样就错了。
因为此时
一点小细节
由于这道题输出的答案是要取模的,如果一个数本来是有值,但取模完后变成零了,得特判一下。
最后给出代码
#include<bits/stdc++.h>
#define int long long
const int N=1e5+5;
using namespace std;
int t,mod=1e9+7;
int in[N],f[N];
vector<int> g[N];
int n,m;
int a[N],b[N],cnt,scc;
int dfn[N],dad[N],low[N],vis[N],s[N],tot;
int c[N],qq[N];
bool pd[N];
void tarjan(int x)//tarjan模版
{
vis[x]=1;
dfn[x]=low[x]=++cnt;
s[++tot]=x;
for(int i=0;i<g[x].size();i++)
{
int y=g[x][i];
if(!dfn[y])
{
tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(vis[y]) low[x]=min(low[x],low[y]);
}
if(low[x]==dfn[x])
{
scc++;
do
{
c[scc]+=qq[s[tot]];
pd[scc]|=bool(qq[s[tot]]);//记录是否真的是零
c[scc]%=mod;
vis[s[tot]]=0;
dad[s[tot]]=scc;
tot--;
}
while(s[tot+1]!=x);
}
}
int cp[N];
bool oh_no[N];
queue<int> q;
void k_s_oh_no()//跑一遍答案的拓扑
{
for(int i=1;i<=scc;i++) cp[i]=in[i];
for(int i=1;i<=scc;i++)
{
if(!in[i]) q.push(i);
}
while(!q.empty())
{
int x=q.front();
for(int i=0;i<g[x].size();i++)
{
c[g[x][i]]+=c[x];
pd[g[x][i]]|=pd[x];
c[g[x][i]]%=mod;
in[g[x][i]]--;
if(!in[g[x][i]]) q.push(g[x][i]);
}
q.pop();
}
}
void tp()//跑一遍判断无限的拓扑
{
for(int i=1;i<=scc;i++) in[i]=cp[i];
for(int i=1;i<=scc;i++)
{
if(!in[i]) q.push(i);
}
while(!q.empty())
{
int x=q.front();
for(int i=0;i<g[x].size();i++)
{
oh_no[g[x][i]]|=oh_no[x];//注意传递无限
in[g[x][i]]--;
if(!in[g[x][i]]) q.push(g[x][i]);
}
q.pop();
}
}
void q_g_w_d()//清零操作
{
tot=scc=0;
for(int i=1;i<=n;i++)
{
g[i].clear();
dfn[i]=pd[i]=low[i]=dad[i]=vis[i]=oh_no[i]=c[i]=cp[i]=in[i]=s[i]=qq[i]=pd[i]=0;
}
}
int iqiq;
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>t;
while(t--)
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i]>>b[i];
g[i].push_back(a[i]);
g[i].push_back(b[i]);
}
for(int i=1;i<=n;i++) cin>>qq[i];
for(int i=1;i<=n;i++)
{
if(!dfn[i]) tarjan(i);
}
for(int i=1;i<=n;i++) g[i].clear();
for(int i=1;i<=n;i++)//缩点
{
int a1=dad[a[i]],b1=dad[b[i]],i1=dad[i];
if(i1!=a1)
{
in[a1]++;
g[i1].push_back(a1);
}
if(b1!=i1)
{
in[b1]++;
g[i1].push_back(b1);
}
}
k_s_oh_no();
for(int i=1;i<=n;i++)//一堆判断
{
if(dad[a[i]]==dad[i]&&dad[b[i]]==dad[i])//指向的两个点和自己在一个连通块里
{
if(pd[dad[a[i]]]||pd[dad[b[i]]]||pd[dad[i]]) oh_no[dad[i]]=1;
}
if(dad[a[i]]==dad[i])//另外一种情况,这两个if类似
{
if(pd[dad[a[i]]]) oh_no[dad[b[i]]]=1;
}
if(dad[b[i]]==dad[i])
{
if(pd[dad[b[i]]]) oh_no[dad[a[i]]]=1;
}
}
tp();
iqiq++;
cout<<"Case #"<<iqiq<<": ";
if(oh_no[dad[1]]) cout<<"UNBOUNDED\n";
else cout<<c[dad[1]]<<"\n";
q_g_w_d();
}
return 0;
}
~都写这么多了,就给个赞吧~。