JZOJ5452. 【NOIP2017提高A组冲刺11.5】轰炸

5 篇文章 0 订阅
1 篇文章 0 订阅

Description

战狂也在玩《魔方王国》。他只会征兵而不会建城市,因此他决定对小奇的城市进行轰炸。
小奇有n 座城市,城市之间建立了m 条有向的地下通道。战狂会发起若干轮轰炸,每轮可以轰炸任意多个城市。
每座城市里都有战狂部署的间谍,在城市遭遇轰炸时,它们会通过地下通道撤离至其它城市。非常不幸的是,在地道里无法得知其它城市是否被轰炸,如果存在两个不同的城市i,j,它们在同一轮被轰炸,并且可以通过地道从城市i 到达城市j,那么城市i 的间谍可能因为撤离到城市j 而被炸死。为了避免这一情况,战狂不会在同一轮轰炸城市i 和城市j。
你需要求出战狂最少需要多少轮可以对每座城市都进行至少一次轰炸。

Input

第一行两个整数n,m。接下来m 行每行两个整数a,b 表示一条从a 连向b的单向边。

Output

输出一行仅一个整数表示答案。

Sample Input

5 4
1 2
2 3
3 1
4 5

Sample Output

3

Data Constraint

对于20%的数据,n,m<=10。
对于40%的数据,n,m<=1000。
对于另外30%的数据,保证无环。
100%的数据,n,m<=1000000。

题解

首先,不要误解题意:“并且可以通过地道从城市i 到达城市j”,这里是到达,而不是直接有边相连。
位于同一条链的点是不能同时炸的,位于同一个强连通分量的也不能同时炸。
所以做法很简单,
先用tarjan缩环,然后再跑一遍最长链。
由于这里的n比较大,所以递归版的tarjan会爆栈,要写人工栈。

code

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#define N 1000003
using namespace std;
char ch;
void read(int& n)
{
    n=0;
    for(ch=getchar();ch<'0' || ch>'9';ch=getchar());
    for(;'0'<=ch && ch<='9';n=(n<<3)+(n<<1)+ch-48,ch=getchar());
}

int n,m,x,y,lb[N][2],fa[N];
int nxt[N*2],to[N*2],b[N],tot,v[N],p[N*2];
int low[N],z[N],top,dfn[N],cnt,now,f[N],ans;
bool bz[N],w[N],pd;
int nxt1[N*2],to1[N*2],b1[N],tot1,f1,f2;
int g[N],dis[N],d[N],l,r;

void ins(int x,int y)
{
    nxt[++tot]=b[x];
    to[tot]=y;
    b[x]=tot;
}

void ins1(int x,int y)
{
    nxt1[++tot1]=b1[x];
    to1[tot1]=y;
    b1[x]=tot1;
    g[y]++;
}

/*void tarjan(int x)
{
    z[++top]=x;
    bz[x]=w[x]=0;
    dfn[x]=low[x]=++now;
    for(int i=b[x];i;i=nxt[i])
        if(bz[to[i]])
        {
            tarjan(to[i]);
            low[x]=min(low[x],low[to[i]]);
        }else if(!w[to[i]])
            low[x]=min(low[x],dfn[to[i]]);

    if(low[x]==dfn[x])
    {
        cnt++;
        z[top+1]=-1;
        while(z[top+1]!=x)
        {
            f[z[top]]=cnt;
            v[cnt]++;
            w[z[top]]=1;
            top--;
        }
        ans=max(ans,v[cnt]);
    }
}*/

void tarjan(int x)
{
    d[r=1]=x;
    while(r)
    {
        x=d[r];
        if(dfn[x]==0)
        {
            z[++top]=x;
            bz[x]=w[x]=0;
            dfn[x]=low[x]=++now;
        }
        if(b[x])
        {
            int i=b[x];
            if(p[i]==0)
            {
                low[x]=min(low[x],low[to[i]]);
            } else
            if(bz[to[i]])
            {
                if(p[i])
                {
                    d[++r]=to[i];
                    p[i]=0;
                    continue;
                }
            }else if(!w[to[i]])
                low[x]=min(low[x],dfn[to[i]]);
            b[x]=nxt[i];
        }
        else
        {   
            if(low[x]==dfn[x])
            {
                cnt++;
                z[top+1]=-1;
                while(z[top+1]!=x)
                {
                    f[z[top]]=cnt;
                    v[cnt]++;
                    w[z[top]]=1;
                    top--;
                }
                ans=max(ans,v[cnt]);
            }
            r--;
        }

    }
}

int get(int x){return x==fa[x]?x:fa[x]=get(fa[x]);}

int main()
{
    freopen("bomb.in","r",stdin);
    freopen("bomb.out","w",stdout);
    read(n);read(m);
    for(int i=1;i<=n;i++)
        fa[i]=i;
    for(int i=1;i<=m;i++)
        read(x),read(y),ins(x,y),lb[i][0]=x,lb[i][1]=y;

    top=now=0;
    memset(bz,1,sizeof(bz));
    memset(w,1,sizeof(w));
    memset(p,1,sizeof(p));
    for(int i=1;i<=n;i++)
        if(bz[i])tarjan(i);

    for(int i=1;i<=m;i++)
        if(f[lb[i][0]]!=f[lb[i][1]])ins1(f[lb[i][0]],f[lb[i][1]]);

    l=r=0; 
    for(int i=1;i<=cnt;i++)
        if(g[i]==0)d[++r]=i;
    while(l<r)
    {
        x=d[++l];
        ans=max(ans,dis[x]+v[x]);
        for(int i=b1[x];i;i=nxt1[i])
        {
            dis[to1[i]]=max(dis[to1[i]],dis[x]+v[x]);
            g[to1[i]]--;
            if(g[to1[i]]==0)d[++r]=to1[i];
        }
    }

    printf("%d",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值