题目传送门:https://www.luogu.org/problemnew/show/P1262
这个题首先tarjan缩个点,然后统计各个缩完之后的点的中被收买人的最小值,假如某个强连通分量的入度为0,那就收买他,我实在是太蒟蒻了想了半天怎么判断NO的情况想不通一直卡在92,后来看了题解恍然大悟。假如一个人不能被收买,那我就直接在targin的时候不搜这个点,那么之后遍历一遍dfn,如果存在0,就说明有一个人既不能被举报,也不能被收买,输出NO和编号即可。附上代码:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int s[100001]={0},low[100001]={0},dfn[100001]={0},co[100001]={0},buy[100001]={0},d1[100001]={0};
int x[100001]={0},y[100001]={0},nxt[100001]={0},head[100001]={0},to[100001]={0},min1[100001]={0},d2[100001]={0};
int tmp=0,num=0,top=0,col=0,n,p,r;
int min(int x,int y)
{
if(x>y)
return y;
else return x;
}
int add(int x,int y)
{
tmp++;
to[tmp]=y;
nxt[tmp]=head[x];
head[x]=tmp;
return 0;
}
int tarjan(int i)
{
int j,t;
num++;top++;
s[top]=i;low[i]=num;dfn[i]=num;
for(j=head[i];j;j=nxt[j])
{
t=to[j];
if(dfn[t]==0)
{
tarjan(t);
low[i]=min(low[i],low[t]);
}
else if(co[t]==0)
{
low[i]=min(low[i],low[t]);
}
}
if(dfn[i]==low[i])
{
col++;
co[i]=col;
if(buy[i]!=0)
min1[col]=buy[i];
while(s[top]!=i)
{
co[s[top]]=col;
if(buy[s[top]]!=0)
min1[col]=min(min1[col],buy[s[top]]);
top--;
}
top--;
}
return 0;
}
int main()
{
int i,j,ans=0,u,t;
scanf("%d%d",&n,&p);
for(i=1;i<=5000;i++)
min1[i]=1000000;
for(i=1;i<=p;i++)
{
scanf("%d",&u);
scanf("%d",&buy[u]);
}
scanf("%d",&r);
for(i=1;i<=r;i++)
{
scanf("%d%d",&x[i],&y[i]);
add(x[i],y[i]);
}
for(i=1;i<=n;i++)
if(dfn[i]==0&&buy[i]!=0)
tarjan(i);
for(i=1;i<=n;i++)
if(dfn[i]==0)
{
printf("NO\n%d",i);
return 0;
}
memset(head,0,sizeof(head));
memset(to,0,sizeof(to));
memset(nxt,0,sizeof(nxt));
tmp=0;
for(i=1;i<=r;i++)
if(co[x[i]]!=co[y[i]])
{
add(co[x[i]],co[y[i]]);
}
for(i=1;i<=col;i++)
{
for(j=head[i];j;j=nxt[j])
{
t=to[j];
d1[i]++;d2[t]++;
}
}
for(i=1;i<=col;i++)
if(d2[i]==0)
ans+=min1[i];
printf("YES\n");
printf("%d",ans);
return 0;
}