1. 题目 间谍网络
【题目描述】(tyvj1153)
由于外国间谍的大量渗入,国家安全正处于高度危机之中。如果A间谍手中掌握着关于B间谍
的犯罪证据,则称A可以揭发B。有些间谍接受贿赂,只要给他们一定数量的美元,他们就愿意交
出手中掌握的全部情报。所以,如果我们能够收买一些间谍的话,我们就可能控制间谍网中的每
一分子。因为一旦我们逮捕了一个间谍,他手中掌握的情报都将归我们所有,这样就有可能逮捕
新的间谍,掌握新的情报。
我们的反间谍机关提供了一份资料,包括所有已知的受贿的间谍,以及他们愿意收受的具体
数额。同时我们还知道哪些间谍手中具体掌握了哪些间谍的资料。假设总共有n个间谍(n不超过
3000),每个间谍分别用1到3000的整数来标识。
请根据这份资料,判断我们是否可能控制全部的间谍,如果可以,求出我们所需要支付的最
少资金。否则,输出不能被控制的一个间谍。
【输入格式】
一行只有一个整数n。
第二行是整数p。表示愿意被收买的人数,1<=p<=n。
接下来的p行,每行有两个整数,第一个数是一个愿意被收买的间谍的编号,第二个数表示他
将会被收买的数额。这个数额不超过20000.
紧跟着一行只有一个整数r,1<=r<=8000。然后r行,每行两个正整数,表示数对(A,B),
A间谍掌握B间谍的证据。
【样例输入】
2
1
2 512
2
1 2
2 1
【样例输出】
YES
512
2. 题目实质
一个有向图,每个节点的代价不同,用最少的代价覆盖最多的子图,已知覆盖一个子图的拓扑序最靠前的节点(入度为零)就可以覆盖这个子图。
3. 算法
Tarjan 求极大强连通分量 + 贪心。
首先,由给出的边数可得,这是一个图,而不是一个树。
既然是图,那么,显然,拓扑序最小(入度为零)的点必须贿赂,不然直接输出 NO 。
然后,很明显这个图中是存在环的,而且,鉴于这个边数很多,用拓扑序判断环路明显不现实,所以这里引入 Tarjan 求极大强连通分量的算法( NOIP 考也是第四题,大家们不用担心)
这个算法的本质就是深搜,主要就是维护两个数组: Dfn (这个节点被深搜到的次序)和 low (这个节点及其以下的节点通过非深搜经过的边所能连接到的最靠上的一个点(最先遍历的一个点)的 Dfn 值)。然后,每当深搜到一个元素时,将这个元素压栈,当栈中某一元素的 low 值达到了栈顶元素时,将他之上的元素全部弹栈,一次性弹出的元素即为一个极大连通分量。
每当找到了一个极大连通分量后,将这个极大连通分量作为一个新的节点存到图 G’ 中,然后这是预处理,接下来开始贪心。
将新得到的这个图进行拓扑排序,如上所述,拓扑序最小的点(入度为零)必须贿赂。如果不能贿赂就输出 NO ,如果能就累加,找到最终的花销。
详细算法:
首先对每个能收买的人处理,记下他能扩展到哪些人。(如果有某个人不能被扩展到,
直接无解跳出)。
然后按价格从大到小排序,从大到小,如果这个人能被2 个或以上(包括他自己)可收
买的人扩展到,就不收买这个人,并将这个人能扩展到的人被扩展的次数减一。
最后,把所有收买的人加起来就行了。
4. 注意事项
这个代码有点难度。
注意不要一上来就拓扑排序。
5. 程序代码
Saltless (Pascal)
program tvyj1153;
var
v,f:array[1..3000]of boolean;
a:array[0..3000,0..3000]of integer;
b:array[1..3000,1..3000]of boolean;
i,j,n,m,p,x,y:integer;
num,deep,nd:integer;
minn:integer;
va,du,mc,rd:array[1..3000]of integer;
tot:longint;
dfn,low,stack:array[1..3000]of integer;
function min(x,y:integer):integer;
begin
if x>y then exit(y)
else exit(x);
end;
procedure zoom(x:integer);
var
list:array[1..3000]of integer;
i,j:integer;
d,l:integer;
min,mint:integer;
begin
min:=32767;
mint:=32767;
d:=0;
l:=0;
while stack[num]<>x do
begin
inc(l);
list[l]:=stack[num];
f[stack[num]]:=false;
dec(num);
end;
f[stack[num]]:=false;
inc(l);
list[l]:=stack[num];
dec(num);
for i:=1 to l do
begin
if list[i]<mint then mint:=list[i];
d:=d+rd[list[i]]; //累加整个分量的度
if(va[list[i]]>0)and(va[list[i]]<min)then min:=va[list[i]];
end;
for i:=1 to l do
for j:=1 to l do
if(i<>j)and(b[list[i],list[j]])then
dec(d); //减去分量内部的度
if(d=0)and(min=32767)then
if mint<minn then minn:=mint; //记录入度为0且不能被贿赂的最小点
inc(nd); //nd记录了G’中节点的个数
du[nd]:=d;
mc[nd]:=min;
end;
procedure dfs(x:integer);
var
i:integer;
begin
inc(deep);
dfn[x]:=deep;
low[x]:=deep;
inc(num);
stack[num]:=x;
f[x]:=true;
for i:=1 to a[x,0] do
if not v[a[x,i]]then
begin
v[a[x,i]]:=true;
dfs(a[x,i]);
low[x]:=min(low[x],low[a[x,i]]);
end
else if f[a[x,i]] then
low[x]:=min(low[x],low[a[x,i]]);
if low[x]=dfn[x] then
zoom(x);
end;
procedure get;
var
i:integer;
begin
if minn<>32767 then //检查是否能
begin
writeln('NO');
writeln(minn);
end
else begin
for i:=1 to nd do
if du[i]=0 then tot:=tot+mc[i];
writeln('YES');
writeln(tot);
end;
end;
begin
readln(n);
readln(p);
fillchar(f,sizeof(f),false);
fillchar(v,sizeof(v),false);
fillchar(b,sizeof(b),false);
minn:=32767;
for i:=1 to p do
begin
read(x);
readln(va[x]);
end;
readln(m);
for i:=1 to m do
begin
readln(x,y);
inc(a[x,0]);
a[x,a[x,0]]:=y;
b[x,y]:=true; //b数组记录连通性
inc(rd[y]); //记录每个点的入度
end;
for i:=1 to n do
if not v[i] then
dfs(i);
get;
end.