Cpp环境【TYVJ1153】【Code[VS]4093】【CQYZOJ16874】 间谍网络

【问题描述】  

  由于外国间谍的大量渗入,国家安全正处于高度的危机之中。如果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

  • 【样例2】

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<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#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;j<gc[i].size();j++)
    {
        int 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<<ans;
    }
    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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值