传送门luoguP1262
通过本题我们不难发现:
1.对于每一个点,那么入度为0必须贿赂,就可以去揭发其他的了
2.对于每一个环,如果一个环上存在一个可以行贿的间谍,那么这个环都可以解决,我们只需要找到每个环上的需要钱的最小值,把它看做一个点。点权值就是最小的钱。这样我们就只需通过找入度为0的点了
首先判断是否有点没有更新,即其他人无法揭露它,它也不能被检举,那么就输出NO
然后我们枚举每一个边所对应的2个点,如果两个点不属于同一连通分量,那么将指向的那个连通分量的入度加+1,最后找到入度为0的点,统计他们要的钱。
代码实现:
#include<bits/stdc++.h>
using namespace std;
#define num ch-'0'
void get(int &res)//快读
{
char ch;bool flag=0;
while(!isdigit(ch=getchar()))
(ch=='-')&&(flag=true);
for(res=num;isdigit(ch=getchar());res=res*10+num);
(flag)&&(res=-res);
}
const int N=1e5+5,M=2e5+5;//一定要开大一点
int p,n,m,low[N],dfn[N],sum,id[N],u[N],v[N],r[N],ans,cnt,mon[N],t,w[N];
bool vis[N];
stack<int>a;
int first[N],nex[M],to[M],tot;
void add(int x,int y)
{
nex[++tot]=first[x];
first[x]=tot;
to[tot]=y;
}
void dfs(int x)//标准的tarjan
{
dfn[x]=low[x]=++cnt;
a.push(x);
vis[x]=1;
for(int i=first[x];i;i=nex[i])
{
int y=to[i];
if(dfn[y]==0)
{
dfs(y);
low[x]=min(low[x],low[y]);
}
else
{
if(vis[y])
{
low[x]=min(low[x],dfn[y]);
}
}
}
if(dfn[x]==low[x])
{
sum++;int t;
do
{
t=a.top();
vis[t]=0;
id[t]=sum;
w[id[t]]=min(w[id[t]],mon[t]);//这里加一个,统计每个强连通的最小钱
a.pop();
}while(t!=x);
}
}
int main()
{
get(n);get(p);
memset(mon,0x3f,sizeof(mon));//表示每个需要的钱,并初始化
memset(w,0x3f,sizeof(w));//表示每个强连通最小需要的钱,并初始化
for(int i=1;i<=p;i++)
{
int num1;
get(num1);
get(mon[num1]);
}
get(m);
for(int i=1;i<=m;i++)
{
int x,y;
get(x);get(y);
u[i]=x;v[i]=y;//记录一下每条边上对应的2个点
add(x,y);
}
for(int i=1;i<=n;i++)
{
if(dfn[i]==0 && mon[i]!=0x3f3f3f3f) dfs(i); //可以被行贿且没有被更新
}
for(int i=1;i<=n;i++)
{
if(dfn[i]==0)//更新不到,且不能贿赂
{
cout<<"NO"<<endl<<i;
return 0;
}
}
//现在我们就把id看做每一个点,缩点。
for(int i=1;i<=m;i++)
{
if(id[u[i]]!=id[v[i]])//如果两点不属于一个连通分量
{
r[id[v[i]]]++;//入度加1
}
}
for(int i=1;i<=sum;i++)
{
if(r[i]==0) ans+=w[i];//入度为0的点必须贿赂
}
cout<<"YES"<<endl;
cout<<ans;
return 0;
}