这个题大概思路就是一发裸的Tarjan然后建一个缩点的图。
读入之后先来一发Tarjan(注意有可能有多个连通图),记录下每个点属于的强连通分量。
然后缩点,建一个DAG,这时根据DAG的结构,肯定存在若干入度为0的点
下面就是重点了,只需要收买(也就是计算这个强连通分量中Minimize的Money)所有入度为0的缩点,也叫根点(自造的词)就一定可以抓完 全图的 间谍。
假如收买了根点之后无法收买缩点u,那么缩点u一定不是根点的子结点,也就意味着u绝对不和这个DAG联通,那u的入度为0(U肯定也是根点),就要收买u,所以收买所有根点是可以抓整个DAG的间谍的。
所以,当根点无法被收买的时候,那无解,统计一下无法被收买的强连通分量(也就是根点)之中最小的点,然后输出即可
或者,有解,那么就统计根点的最小花费,加起来就OK。
不需要DFS/BFS。统计的时候可以玩枚举(强连通分量不会太多……)
#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
using namespace std;
//建图
struct edge
{
int pre,to;
} edge[8010]={0};
int pointer[3010]={0},fx,tx,edgenum/*边数*/,nodenum/*点数*/,canbuynum/*带权点数*/;
//Tarjan算法
int dfn[3010]={0},low[3010]={0},tstamp=0/*时间戳*/,header=0/*栈指针*/,stack[10010]={0}/*手动栈*/;
bool init[3010]={false};
//记录强连通分量以及缩点图
int numbers[3010]={0}/*点属于哪个分量*/,counter=0/*记录强连通分量个数*/,/*缩点图*/inw[3010]={0},prex2;
int moneyx[3010]/*点权记录*/,cache1,cache4;//上面两个缓存是读点权的
//答案处理
long long minimoney,ans=0;//每个分量最小花费和总答案
bool flag=false,flag2=false;//flag2是判断有没有一个缩点是无法被收买的 flag记录枚举过程中的点
int mininode=10000,mininode2=10000;//没法收买的最小的点(第一个是枚举过程中的缓存)
//链式前向星建图
void Insert(int at,int fromx,int tox)
{
edge[at].pre=pointer[fromx];
edge[at].to=tox;
pointer[fromx]=at;
return;
}
//Tarjan模板
void tarjan(int u)
{
int cache2,cache3,prex;
dfn[u]=low[u]=++tstamp;
stack[++header]=u; init[u]=true;
prex=pointer[u];
while (prex>0)
{
cache2=edge[prex].to;
if (dfn[cache2]==0)
{
tarjan(cache2);
low[u]=min(low[u],low[cache2]);
}
else if (init[cache2]==true) low[u]=min(low[u],dfn[cache2]);
prex=edge[prex].pre;
}
if (low[u]==dfn[u])
{
counter++;
do
{
cache3=stack[header];
header--;
numbers[cache3]=counter;
init[cache3]=false;
} while (cache3!=u);
}
return;
}
void Shrink()
{
for (int i=1;i<=nodenum;i++)
{
prex2=pointer[i];
while (prex2>0)
{
if (numbers[i]!=numbers[edge[prex2].to])//一条边连接的两个点如果不处于一个强连通分量之中
{
inw[numbers[edge[prex2].to]]++;//增加入度和出度
}
prex2=edge[prex2].pre;//链式前向星……
}
}
return;
}
void getminimoney(int u)
{
minimoney=100000; flag2=false; mininode=10000;//初始化
for (int j=1;j<=nodenum;j++)
{
//cout<<"点 "<<j<<" 属于缩点 "<<numbers[j]<<" Money= "<<moneyx[j]<<endl;
if (numbers[j]==u) //枚举到点j,j属于分量u
{
if (moneyx[j]==-1 && mininode>j) mininode=j;//记录没法被收买的最小点
else if (moneyx[j]!=-1)//如果可以被收买
{
if (minimoney>moneyx[j]) minimoney=moneyx[j];//记录花费的最小金额
flag2=true;//这个强连通分量可以被收买
}
}
}
return;
}
int main()
{
//读入数据
scanf("%d%d",&nodenum,&canbuynum);
for (int i=0;i<=nodenum;i++) moneyx[i]=-1;
for (int i=1;i<=canbuynum;i++)
{
scanf("%d%d",&cache1,&cache4);
moneyx[cache1]=cache4;
}
scanf("%d",&edgenum);
for (int i=1;i<=edgenum;i++)
{
scanf("%d%d",&fx,&tx);
Insert(i,fx,tx);
}
//Tarjan求强连通分量
for (int i=1;i<=nodenum;i++) if (numbers[i]==0) tarjan(i);
//缩点
Shrink();
for (int i=1;i<=counter;i++) if (inw[i]==0)
{
//cout<<"缩点 "<<i<<" 入度为0"<<endl;
getminimoney(i);
if (flag2==false)
{
//cout<<"缩点发现无法收买的根节点!根节点="<<i<<endl;
flag=true;//缩点发现无法收买的根节点
if (mininode2>mininode) mininode2=mininode;//记录最小点
}
else ans+=minimoney;
//cout<<"强连通分量 "<<i<<" 最小花费= "<<minimoney<<endl;
//cout<<"ans= "<<ans<<endl<<endl;
}
if (flag==true) printf("NO\n%d",mininode2);
else printf("YES\n%d",ans);
return 0;
}