洛谷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,并在第二行输出不能控制的间谍中,编号最小的间谍编号。
输入输出样例
4 2 1 100 4 200 2 1 2 3 4
NO 3
#include<cstdio> #include<algorithm> using namespace std; int n,p,r,cnt,tot; int money[3001]; int to[8001],nxt[8001],head[3001]; int deep[3001],rudu[3001],low[3001],v[3001],z[3001],inz[3001],belong[3001],ans,top,minmoney[3001]; void tarjan(int x) { deep[x]=low[x]=++tot; v[x]=1; inz[x]=1; z[++top]=x; for(int i=head[x];i;i=nxt[i]) { int y=to[i]; if(v[y]==0) tarjan(y),low[x]=min(low[x],low[y]); else if(inz[y]==1) low[x]=min(low[x],deep[y]); } if(deep[x]==low[x]) { minmoney[x]=2147483647; int t; do { t=z[top--]; inz[t]=0; belong[t]=x; minmoney[x]=min(minmoney[x],money[t]); }while(t!=x); } } void add(int i,int x,int y) { to[i]=y; nxt[i]=head[x]; head[x]=i; } void dfs(int x) { if(v[x]) return; v[x]=1;cnt++; for(int i=head[x];i;i=nxt[i]) dfs(to[i]); } int main() { scanf("%d%d",&n,&p); for(int i=1;i<=n;i++) money[i]=2147483647; for(int i=1;i<=p;i++) { int x,y; scanf("%d%d",&x,&y); money[x]=y; } scanf("%d",&r); for(int i=1;i<=r;i++) { int a,b; scanf("%d%d",&a,&b); add(i,a,b); } for(int i=1;i<=n;i++) if(money[i]!=2147483647) dfs(i); if(cnt<n) { for(int i=1;i<=n;i++) if(v[i]==0) { printf("NO\n%d",i); return 0; } } for(int i=1;i<=n;i++) v[i]=0; for(int i=1;i<=n;i++) if(v[i]==0) tarjan(i); for(int i=1;i<=n;i++) for(int j=head[i];j;j=nxt[j]) if(belong[i]!=belong[to[j]]) rudu[belong[to[j]]]++; for(int i=1;i<=n;i++) if(rudu[i]==0 && belong[i]==i) ans+=minmoney[i]; printf("YES\n%d",ans); return 0; }
先从主函数说起,输入没商量,我先把money数组全部置成了极大值,更新后依然是极大值的说明不能被收买。这里注意不能用memset();不信你就试一试。
money[x]=y;表示编号为x的间谍可以用y钱收买
加边函数稍微优化一些,用i直接表示编号,可以节省一个Int类型的数据,防止计数器太多自己都看不懂。
接着是dfs,遍历有向图。
先处理NO的情况,如果遍历后的节点数目小于n,说明不能被全部收买,那么不连通的点就是不能收买的间谍,所以解决了NO的问题。
为了省空间,把v数组重新置零。
开始跑tarjan,缩点,不会的请自行百度。
这里需要多说一下,这道题的tarjan函数与正常模板不一样的是,我们不仅要用belong数组说明这个点属于哪个强连通分量,而且还要维护minmoney,也就是收买这个强连通分量里所有间谍所用的最少钱数,这一点很关键!!!必须理解明白
统计入度。最后输出的时候进行判断,入度为0的点是强连通分量的起点,收买了这个点就可以收买强连通分量里所有的间谍,并且此点所属的分量编号就是它自己,那么满足条件,ans累加。
输出即可。