初见安~这里是传送门:洛谷P1262
题目描述
由于外国间谍的大量渗入,国家安全正处于高度的危机之中。如果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间谍的证据。
输出格式:
如果可以控制所有间谍,第一行输出YES,并在第二行输出所需要支付的贿金最小值。否则输出NO,并在第二行输出不能控制的间谍中,编号最小的间谍编号。
输入样例#1:
3
2
1 10
2 100
2
1 3
2 3
输出样例#1:
YES
110
输入样例#2:
4
2
1 100
4 200
2
1 2
3 4
输出样例#2:
NO
3
题解
很明显的——把所有关系连起来就是一个图。并且贿赂一个人后可以牵扯到其他很多人。这里就有两种情况:
1.有一个环的话,那么要贿赂这个环里的所有人,只需要贿赂资金最少的那个。
2.没有环的话,贿赂入度为0的那一个即可。
处理环的话,因为要确保找到的资金最少的那个一定可以直接or间接贿赂到这个环里的其他所有人,所以这一定是一个强连通分量。 所以就会用到我们的Tarjan算法进行缩点~(Tarjan算法传送门)至于非环的情况——我们也可以用tarjan的思路来一并处理。因为tarjan缩点后,判断各个强连通分量之间的连边靠的是判断这两个点所在强连通分量是否相同,不同则连,所以即使不是环,在这个时候也可以被考虑到。而如果说这个非环子图中有不止一个入度为0的点呢?这个其实可以忽略不考虑的:)因为反正说起来都是要主动贿赂的,都要付钱,入度都为0,结果是一样的。最后的花费就是各个强连通分量的最小花费金额的总和,最小花费最好开一个数组存储,因为便利一些的方法是在tarjan找到强连通分量的时候在弹出stack时就顺便存下来,而后就不用再把这个强连通分量的点存下来再遍历了。
以上是关于输出“YES“的解释。那如果是要输出NO呢?
因为要输出最小的一个贿赂不到的结点,所以我们倒着想很容易想到for 1~n来筛。那么怎么判断呢——看这个点有没有被贿赂的机会。所以很容易想到的——一开始遍历tarjan的时候,就确定开始的这个点是可以被贿赂的,随后我们只需要看有没有dfn没有被更新的点就可以了:)就类似于检查一遍,通过了检查就可以开始存入度了、
下面是代码及详解——
#include<bits/stdc++.h>
#define maxn 3005
using namespace std;
int n, r, p, w[maxn];
struct node
{
int to,nxt;
node(){}
node(int tt,int nn)
{
to = tt; nxt = nn;
}
}e[maxn * 3];
int k = 0, head[maxn];
void add(int u,int v)
{
e[k] = node(v, head[u]);
head[u]=k++;
}
int dfn[maxn], low[maxn], tot=0, top=0, stc[maxn], cnt=0, col[maxn], mini[maxn];
bool vis[maxn];
void tarjan(int x)//核心代码,详解见传送门
{
dfn[x]=low[x]=++tot;
stc[++top]=x;
vis[x]=1;
int y;
for(register int i=head[x];~i;i=e[i].nxt)
{
y=e[i].to;
if(!dfn[y])
{
tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(vis[y])
{
low[x]=min(low[x],dfn[y]);
}
}
if(dfn[x]==low[x])
{
cnt++;
do
{
y=stc[top--];
vis[y]=0;
col[y]=cnt;
mini[cnt] = min(w[y], mini[cnt]);//这一步取min,所以两个数组都要初始化极大值
}while(x!=y);
}
}
int a, b, ind[maxn], ans=0;
int main()
{
memset(head, -1, sizeof head);
memset(w, 0x3f, sizeof w);
memset(mini, 0x3f, sizeof mini);
scanf("%d%d", &n,&p);
for(register int i = 1; i <= p; i++)
scanf("%d", &a),scanf("%d", &w[a]);//必须分开读入
scanf("%d", &r);
for(int i = 1; i <= r; i++)
{
scanf("%d%d", &a, &b);
add(a,b);
}
for(int i = 1; i <= n; i++)
{
if(!dfn[i] && w[i] != 0x3f3f3f3f) tarjan(i);//确定点i是可以贿赂的,金额更新过
}
for(int i = 1; i <= n; i++)
{
if(!dfn[i])
{
puts("NO");//puts自动换行
printf("%d\n", i);
return 0;
}
}
for(int i = 1; i <= n; i++)
{
for(int j = head[i]; ~j; j = e[j].nxt)
{
int y = e[j].to;
if(col[i] != col[y]) ind[col[y]]++;//ind存这一强连通分量入度
}
}
for(int i = 1; i <= cnt; i++)
{
if(!ind[i])//入度为0,必须贿赂
{
ans+=mini[i];//ans累计
}
}
printf("YES\n%d\n", ans);
return 0;
}
最后——特别提醒一下(我竟然今天才知道这种操作):用maxn时,这里是直接写的3000,确实n的范围也不过3000。但我一开始写的是3000+5,第二组数据就一直出错。甚至发现修改结构体e的数据范围也可以改变答案(但是改不对就对了)。这个地方,如果写3005的话,传过去3005*3一点问题都没有,但是如果是3000+5的话就相当于3000+5 * 3=3015。这就是个很悲伤的故事了:)
迎评:)
——End——