P1262 间谍网络 (tarjan缩点 水过去)

题目描述

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

说明

懒得多说直接上代码了。

#include<bits/stdc++.h>
using namespace std;
inline int read()
{
    int x=0; char c=getchar();
    while(!isdigit(c)) c=getchar();
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x;
}
int n,p,r,pat,top,tim,ans;
//pat表示边数,part表示强连通分量数,top表示栈高,tim表示时间戳 
int mon[3100];
//money 
int blg[3100],fa[3100];
//belong、father(其实两者本质是一样的,关于father等会儿会讲) 
int dfn[3100],low[3100];
//深度优先(英文)、lowest(应该是吧) 
int stk[3100],head[3100];
//stack(栈)、head是邻接链表的头(其实我觉得更像是尾) 
bool instk[3100];
//instack表示是否在栈里面 
struct tpat
{
    int v,next;
}edge[8100];

void add(int u,int v)
{
    edge[pat].next=head[u];
    edge[pat].v=v;
    head[u]=pat++;
} //邻接表存边( 比向量vector的push_back()要快 ) 

void tarjan(int u)   //tarjan缩点不详解,自行百度 
{
    dfn[u]=low[u]=++tim;
    instk[u]=true; stk[++top]=u;
    for(int i=head[u];i!=-1;i=edge[i].next) //遍历点u的邻接表 
    {
        int v=edge[i].v;
        if(!dfn[v])
            tarjan(v),low[u]=min(low[u] , low[v]);
        else if(instk[v]) //这点很重要!被搜到过但不在栈内的话就不能操作 
            low[u]=min(low[u] , dfn[v]);
    }
    if(dfn[u]==low[u]) //low无变化代表u是一个强连通分量的出发点 
    {
        int j;
        do{
            j=stk[top--];//从栈顶一直弹弹弹(弹走鱼尾纹),弹到u点(当前点) 
            mon[u]=min(mon[u] , mon[j]);
            instk[j]=false; //出栈 
            blg[j]=u;
            //注意这里就是把belong[j]赋值为u了,方便缩点后的操作 
        }while(j!=u);  //弹到当前节点就退出 
    }
}

void solve()
{
    for(int i=1;i<=n;++i) //fa[i]都赋为本身 
        fa[i]=i;
    for(int i=1;i<=n;++i)
    for(int j=head[i];j!=-1;j=edge[j].next)
        if(blg[i]!=blg[edge[j].v]) fa[blg[edge[j].v]]=blg[i];
                                //这里是不能把fa换成blg的,举个例子吧,
//如果只有三个点,然后有两条边使两个不同的点分别指向同一个点的话...
//想必你也猜到了,我也栽在这儿过~~~  _(:з」∠)_ 
    for(int i=1;i<=n;++i)
    {
        if(blg[i]!=i || fa[i]!=i)  //不是必须要付费的间谍就continue 
            continue; 
        if(mon[i]==0x3f3f3f3f)  //如果说这个点无穷大就直接输出+退出
        //话说我怎么觉得这里是有bug的来着?貌似我自己都能把自己hack掉...
        //额...不过洛谷上是水过去了... _(:з」∠)_ 下文再放个图试试? 
        {
            printf("NO\n%d\n",i);
            return ; //输完直接退 
        }
        ans+=mon[i]; //否则答案累加 
    }
    printf("YES\n%d\n",ans);
}

int main()
{
    n=read();p=read();
    memset(mon,0x3f,sizeof(mon));
    memset(head,-1,sizeof(head));
    for(int i=0;i<p;++i)
        mon[read()]=read();
    r=read();
    for(int i=0;i<r;++i)
    { int u=read(),v=read(); add(u,v); }


    for(int i=1;i<=n;++i) //缩点 
        if(!dfn[i])
            tarjan(i);

    solve();    //缩点完开始正式工作 
    return 0;
}

(看我注解写的多认真,你能忍心不点个赞吗? _ (:з」∠)_ )

然后我来说说我是怎么hack掉自己的程序的 … -_-|| (方便起见,假设没有一个间谍能被收买)

6,hack了

然后这就hack了?洛谷上还水过去了?心中一万匹草泥马在奔腾
尼玛

然后呢?标程在哪里?

然我先想想…emmm好了上代码吧!

其实…只要把solve函数改成这样就好了:

#include<bits/stdc++.h>
using namespace std;
inline int read()
{
    int x=0; char c=getchar();
    while(!isdigit(c)) c=getchar();
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x;
}
int n,p,r,pat,top,tim,ans;
int mon[3100],mnode[3100];
//多开了一个mnode表示:如果该点是其所在的强连通分量的出发点,则该强连通分量中最小的点的编号
int blg[3100],fa[3100];
int dfn[3100],low[3100];
int stk[3100],head[3100];
bool instk[3100];
struct tpat
{
    int v,next;
}edge[8100];

void add(int u,int v)
{
    edge[pat].next=head[u];
    edge[pat].v=v;
    head[u]=pat++;
} 

void tarjan(int u)
{
    dfn[u]=low[u]=++tim;
    instk[u]=true; stk[++top]=u;
    for(int i=head[u];i!=-1;i=edge[i].next) 
    {
        int v=edge[i].v;
        if(!dfn[v])
            tarjan(v),low[u]=min(low[u] , low[v]);
        else if(instk[v]) 
            low[u]=min(low[u] , dfn[v]);
    }
    if(dfn[u]==low[u]) //low无变化代表u是一个强连通分量的出发点 
    {
        int j;
        do{
            j=stk[top--];//从栈顶一直弹弹弹(弹走鱼尾纹),弹到u点(当前点) 
            mon[u]=min(mon[u] , mon[j]);
            instk[j]=false; //出栈 
            blg[j]=u;
            if(mnode[j]==4000 || mnode[u]==4000)
            //如果强连通分量中有人可以收买,则整个强连通分量中的人都可以被控制
                mnode[u]=4000;
            else mnode[u]=min(mnode[u] , mnode[j]);
        }while(j!=u);
    }
}

void solve()
{
    for(int i=1;i<=n;++i)
    for(int j=head[i];j!=-1;j=edge[j].next)
      if(blg[i]!=blg[edge[j].v])
        fa[blg[edge[j].v]]=blg[i];

    for(int i=1;i<=n;++i)
    {
        if(blg[i]==i)
            mnode[fa[i]]=min(mnode[fa[i]] , mnode[i]);
        //这里的意思是在某个强连通分量的出发点时将改点前驱的强连通分量的出发点取编号为min的操作
        if(blg[i]!=i || fa[i]!=i)
            continue;
        if(mon[i]==0x3f3f3f3f)
        {
            printf("NO\n%d\n",mnode[i]); //然后这里输出就变成了当前点的最小编号
            return ;
        }
        ans+=mon[i];
    }
    printf("YES\n%d\n",ans);
}

int main()
{
    n=read();p=read();
    memset(mon,0x3f,sizeof(mon));
    memset(head,-1,sizeof(head));
    for(int i=0;i<p;++i)
        mon[read()]=read();
    r=read();
    for(int i=0;i<r;++i)
    { int u=read(),v=read(); add(u,v); }
    for(int i=1;i<=n;++i)
    { mnode[i]=fa[i]=i ; if(mon[i]!=0x3f3f3f3f) mnode[i]=4000; }
        //可以收买的就直接为最大4000

    for(int i=1;i<=n;++i)
        if(!dfn[i])
            tarjan(i);

    solve();
    return 0;
}

珍惜无语,洛谷数据是真水啊,圣水!(再吐槽一波)

OK,本篇blog的正文到此结束,然后我是无力吐槽了,累得要死 _ (:з」∠) _
各位看官们下次见!

猜你喜欢

转载自blog.csdn.net/qq_35416331/article/details/79794112
今日推荐