城市轰炸

城市轰炸

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

……比赛的时候拿到这题我直接过了,不会做。
【题目大意】:给你n个点m条边的有向图,要求可到达的点不能同时炸毁,问炸毁n个点需要多少次?
其实当时题意我是没懂。。。
题解:

考虑无环的情况,显然答案为最长链长度,证明如下:
1、由于最长链上的点两两不能同时轰炸,因此最优解>=最长链长度。
2、令fi表示从i出发的最长链长度,那么在第fi轮轰炸i,可以得到一个恰好为最长链长度的方案,因此最优解<=最长链长度。
容易发现将一个大小为x的强连通分量替换成一个长度为x的链,所有点两两之间的连通关系不变。
时间复杂度O(n+m)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
void fff(){
    freopen("bomb.in","r",stdin);
    freopen("bomb.out","w",stdout);
}
const int MAXN=1000100;
int nxt[MAXN],first[MAXN],en[MAXN],tot=0;
void add(int x,int y){
    nxt[++tot]=first[x];
    first[x]=tot;
    en[tot]=y;
}
int d[MAXN],f[MAXN],q[MAXN];
int nxt1[MAXN],first1[MAXN],en1[MAXN];
void add1(int x,int y){
    nxt1[++tot]=first1[x];
    first1[x]=tot;
    en1[tot]=y;
    d[y]++;
}
int n,m;
bool vis[MAXN],bz[MAXN];
int stack[MAXN],MS[MAXN],dfn[MAXN];
int g[MAXN],low[MAXN],r[MAXN],bel[MAXN];
int top,num;
void tarjan(int x){
    MS[MS[0]=1]=x;
    while (MS[0]){
        if(!dfn[x=MS[MS[0]]]){
            dfn[x]=low[x]=++tot;
            r[stack[++top]=x]=first[x];
            bz[x]=vis[x]=true;
        }
        bool enter=false;
        for (int i=r[x];i;i=nxt[i]){
            if(!vis[en[i]]){
                r[x]=nxt[i];
                MS[++MS[0]]=en[i];
                enter=true;
                break;
            }else{
                if(bz[en[i]]){
                    low[x]=min(low[x],dfn[en[i]]);
                }
            }
        }
        if(enter) continue;
        if(dfn[x]==low[x]){
            num++;
            do{
                bel[stack[top]]=num;
                bz[stack[top--]]=false;
            }while(stack[top+1]!=x);
        }
        low[MS[--MS[0]]]=min(low[MS[MS[0]]],low[x]);
    }
}

int ans=0;
int main(){
    fff();
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y);
    }
    tot=0;
    for (int i=1;i<=n;i++){
        if(!vis[i]) tarjan(i);
    }
    tot=0;
    for(int i=1;i<=n;i++){
        g[bel[i]]++;
        for (int j=first[i];j;j=nxt[j])
            if(bel[i]^bel[en[j]]) add1(bel[i],bel[en[j]]);
    }
    int l=0,r=0;
    for (int i=1;i<=n;i++){
        if(!d[i])f[q[++r]=i]=g[i];
    }
    while (l<r){
        int x=q[++l];
        if(!first1[x]) ans=max(ans,f[x]);
        for (int i=first1[x];i;i=nxt1[i]){
            f[en1[i]]=max(f[en1[i]],f[x]+g[en1[i]]);
            if(!--d[en1[i]]) q[++r]=en1[i];
        }
    }
    printf("%d\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值