【问题描述】
由于外国间谍的大量渗入,国家安全正处于高度的危机之中。如果A间谍手中掌握着关于B间谍的犯罪证据,则称A可以揭发B。有些间谍收受贿赂,只要给他们一定数量的美元,他们就愿意交出手中掌握的全部情报。所以,如果我们能够收买一些间谍的话,我们就可能控制间谍网中的每一分子。因为一旦我们逮捕了一个间谍,他手中掌握的情报都将归我们所有,这样就有可能逮捕新的间谍,掌握新的情报。
我们的反间谍机关提供了一份资料,包括所有已知的受贿的间谍,以及他们愿意收受的具体数额。同时我们还知道哪些间谍手中具体掌握了哪些间谍的资料。假设总共有n个间谍(n不超过3000),每个间谍分别用1到n的整数来标识。
请根据这份资料,判断我们是否有可能控制全部的间谍,如果可以,求出我们所需要支付的最少资金。否则,输出不能被控制的一个间谍。
【输入格式】
第一行只有一个整数n。
第二行是整数p。表示愿意被收买的人数,1≤p≤n。
接下来的p行,每行有两个整数,第一个数是一个愿意被收买的间谍的编号,第二个数表示他将会被收买的数额。这个数额不超过20000。
紧跟着一行只有一个整数r,1≤r≤8000。然后r行,每行两个正整数,表示数对(A, B),A间谍掌握B间谍的证据。
【输出格式】
如果可以控制所有间谍,第一行输出YES,并在第二行输出所需要支付的贿金最小值。否则输出NO,并在第二行输出不能控制的间谍中,编号最小的间谍编号。
【输入样例】
【样例1】
3
2
1 10
2 100
2
1 3
2 3
【样例2】
4
2
1 100
4 200
2
1 2
3 4
【输出样例】
【样例1】
YES
110
NO
3
【数据范围】
n不超过3000
【思路梳理】
看到网上诸路大神们用的都是tarjan算法,笔者心生不服之意:世界上求强连通分量的算法那么多,岂是一家tarjan能言尽的!不服归不服,笔者当即就用Kosaraju算法重新写了一遍这个题,大致做出如下思路梳理。
跟Kosaraju的经典思想一样,我们先考虑缩点。显然来说,同一强连通分量的点(即一群能够相互揭发的间谍)可以视为同一个点,如下图所示:
如图所示,a:{1,2,3},b:{4},c:{5},d:{6,7,8,9,10}分别是不同的强连通分量,那么显然对于每一个局部来看:
我们可以通过收买1来掌握第一个强连通分量中所有的点,收买4来掌握第2个强连通分量,收买6或者是7来掌握第四个强连通分量(事实上应该收买7,因为这样的代价是15没有更优的局部方案),第三个强连通分量则局部来看不能够被收买。
那么由此一来,10个点的图就被缩成了4个结点a,b,c,d,且当前的图一定是一个DAG图(详见Kosaraju的原理,百度百科)。缩点完成后我们以新的结点建立新的图,如下所示:
下面我们统筹全局来看,现在可能会出现两种情况:
1.存在某些点i,他们无论如何都不能够被掌控
那么这些结点i(间谍群)需要同时满足如下的两个条件:
1).结点i(间谍群)的入度等于0;
2).这个结点i(间谍群)的内部不存在任何一个结点(间谍)可以被收买。
2.任何一个结点i都能够被掌控
那么所有的结点(间谍群)需要满足如下的任意一个条件:
1).结点i(间谍群)的入度不等于0,并且从某一个能够被收买的间谍群j出发能够访问到i;
2).结点i(间谍群)的内部存在一个或多个结点(间谍)可以被收买。
看起来此时实现不会出现问题了,在新的DAG图上从每一个存在能够被收买的间谍的结点i(间谍群)出发进行DFS/BFS,对收买每一个间谍群的价格进行记录(如果不能够被收买则记为-1或者无穷大inf)。完毕之后考察收买每一个强连通分量(间谍群)的价格,记录是否存在不能够被收买的强连通分量(间谍群),如果有就是上述情况的1,输出“NO”,否则则是上述情况的2“YES”。
如果是“NO”很好实现:顺次考察每一个原来的结点(间谍),查看他所在的强连通分量(间谍群)能否被收买。输出第一个所在强连通分量(间谍群)不能被收买的结点(间谍)即可。
但如果是情况2“YES”怎么输出最小的代价呢?很简单,统计收买所有入度为0的强连通分量(间谍群)的代价和即可。因为这些强连通分量(间谍群)不可能被其它间谍/间谍群揭发(入度为0),那么我们只能花钱在这些强连通分量上。而其它所有的结点入度不为0,存在某一条路径从入度为0的被收买间谍群出发,访问到他们。
好了,啰嗦了那么多,低贱的笔者给出Kosaraju代码如下:
【Cpp代码】
#include
#include
#include
#include
#define maxn 3005
#define inf 200005
using namespace std;
int g[maxn][maxn],gr[maxn][maxn];
int n,p,r,rd[maxn],cd[maxn],belong[maxn],scc=0,buy[maxn],price[maxn];
bool vis[maxn],flag=true;
vector<int>vs,gc[maxn];
long long ans=0;
void DFS(int i)
{
vis[i]=true;
for(int j=1;j<=n;j++)if(g[i][j])
{
if(vis[j]) continue;
DFS(j);
}
vs.push_back(i);
}
void DFSr(int i,int scc)
{
belong[i]=scc;
for(int j=1;j<=n;j++)if(gr[i][j])
{
if(belong[j]) continue;
DFSr(j,scc);
}
}
void DFSsd(int i)//注意这里的结点i、结点k都是缩点后的强连通分量
{
for(int j=0;jint k=gc[i][j];
if(buy[k]<=buy[i]) continue;
buy[k]=buy[i];
DFSsd(k);
}
}
void find_scc()//经典的Kosaraju算法
{
memset(vis,false,sizeof(vis));
for(int i=1;i<=n;i++)if(!vis[i])//1次DFS,求后序序列
DFS(i);
for(int i=vs.size()-1;i>=0;i--)if(!belong[vs[i]])
DFSr(vs[i],++scc);//2次DFS,统计强连通分量,标记结点i所属的强连通分量为belong[i]
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)if(g[i][j] && belong[i]!=belong[j])//在新的以强连通分量(间谍群)为结点的基础上建立DAG图
{
gc[belong[i]].push_back(belong[j]);//i间谍群能够揭发j间谍群
cd[belong[i]]++;//i间谍群的出度(i能够揭发的间谍群数),可以不统计无伤大雅
rd[belong[j]]++;//j间谍群的入度(能够揭发j的间谍群数)
}
}
void task()
{
for(int i=1;i<=n;i++) buy[i]=inf;//收买每一个间谍群的价格先初始化为无穷大或者-1
for(int i=1;i<=n;i++)if(price[i]<=20000)//i是能够被收买的间谍,考察能否收买它使得收买其它间谍群的代价变小
{
if(buy[belong[i]]<=price[i]) continue;//如果连收买间谍i所在的间谍群的代价都不能减少就不用考虑了
buy[belong[i]]=price[i];//更新收买间谍i所在的间谍群的最小代价
DFSsd(belong[i]);
}
for(int i=1;i<=scc;i++)if(buy[i]==inf)//存在某一个强连通分量(间谍群)不能够被收买
{flag=false;break;}
if(flag)
{
printf("YES\n");
for(int i=1;i<=scc;i++)if(rd[i]==0)//入度为0,不能被收买
ans+=buy[i];
cout<else
{
printf("NO\n");
for(int i=1;i<=n;i++)if(buy[belong[i]]==inf)//间谍i所在的间谍群不能够被收买
{printf("%d",i);break;}
}
}
int main()
{
scanf("%d%d",&n,&p);
for(int i=1;i<=p;i++)//能被收买的间谍
{
int x,y;
scanf("%d%d",&x,&y);
price[x]=y;
}
scanf("%d",&r);
for(int i=1;i<=r;i++)
{
int x,y;
scanf("%d%d",&x,&y);
g[x][y]++;//笔者比较懒用了邻接矩阵,如果在某些题库上超时的话可以考虑邻接表
gr[y][x]++;
}
find_scc();
task();
return 0;
}