分析
很明显,这道题与节点的度有关。如果一个点的入度为0,则我们必然要贿赂他。但是如果单纯的考虑度就错了。我们忽略了一种入度全部大于0的情况——环。样例就是一个例子。这时如果我们再拓扑找环再去找最小值,我们就会花大量时间(毕竟边很多)。这时就要用到Tarjan缩点。
Tarjan是一种很高效的求解有向图的强连通分量的算法,但是它的主要应用之一是缩点,也就是把整个强连通分量的一定信息集中到一个点上,将其构成一个新图。由于所有强连通分量的并集是所有点的并集,所以整个图的相应性质不变。
我们把图G转化成一个由代表强连通分量的点构成的G’。我们只需记录该分量中最小的节点权值。然后在G’中找到入度为0的点,枚举是否能被贿赂,如果不能则输出NO,否则累加,到最后输出。
这道题主要还是当做 tarjan 模板来做
代码
#include<bits/stdc++.h>
#define N 3009
#define in read()
using namespace std;
int n,p,r;
inline int read(){
char ch;int res=0;
while((ch=getchar())<'0'||ch>'9');
while(ch>='0'&&ch<='9'){
res=(res<<3)+(res<<1)+ch-'0';
ch=getchar();
}
return res;
}
struct node{bool co;int pay;} a[N];
int nxt[8009],head[N],to[8009],cnt;
int dfn[N],low[N],dfs=0,sum=0,id[N],du[N],pp[N],mi[N];
bool insta[N];
stack<int > s;
void add(int x,int y){ nxt[++cnt]=head[x];head[x]=cnt;to[cnt]=y;}
void tarjan(int x){
s.push(x);insta[x]=1;
dfn[x]=++dfs;low[x]=dfn[x];
for(int i=head[x];i;i=nxt[i]){
int y=to[i];
if(!dfn[y]){
tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(insta[y]) low[x]=min(low[x],dfn[y]);
}
int i;
if(dfn[x]==low[x]){
sum++;int minn=20009,minnh=3009;
do{
i=s.top();s.pop();
insta[i]=0;id[i]=sum;
minnh=min(minnh,i);//记录这个环中最小的编号
if(a[i].co) minn=min(minn,a[i].pay);//记录这个环中最小贿赂
}while(i!=x);
pp[sum]=minn;mi[sum]=minnh;
}
}
int main(){
int ans=0;
n=in;p=in;
memset(a,0,sizeof(a));
for(int i=1;i<=p;++i)
{
int k=in;
a[k].co=1;a[k].pay=in;
}
r=in;
int x,y;
for(int i=1;i<=r;++i){
x=in;y=in;
add(x,y);
}
for(int i=1;i<=n;++i) if(!dfn[i]) tarjan(i);
for(int i=1;i<=n;++i){
for(int e=head[i];e;e=nxt[e]){
int j=to[e];
if(id[j]!=id[i]&&(pp[id[i]]<20009||du[id[i]]!=0)) du[id[j]]++;
//只有当这个“点”之前的那个点是可以被到达/或被直接贿赂,这个点才可以增加度数
}
}
int num=0,flag=1,minn=3009;
for(int i=1;i<=sum;++i){
if(!du[i]&&pp[i]==20009) {
flag=0;
minn=min(minn,mi[i]);
}
if(!du[i]&&pp[i]<20009) ans+=pp[i];
}
if(flag) printf("YES\n%d",ans);
else printf("NO\n%d",minn);
return 0;
}