【图论】Ural 1557

Ural 1557  <---原题连接


题目大意为,两个黑客攻击一个相互连通的网络。网络中有n台计算机和m条链路。每一个人可以选择一条链路进行攻击并将其截断,问在这两个人攻击完成后,能够将这个网络分割成至少两个部分的方案由多少种。

需要注意的是,两名黑客不会攻击同一条链路。这也就说明,如果网络中仅有两个节点和一条边,答案应该是0(黑客不会同时攻击这一条链路,所以没法完成任务)

数据范围:

1 ≤ N ≤ 2000; 

0 ≤ M ≤ 100000;

首先,如果攻击的两条边中有一条是“桥”,那么剩下的一条边可以任意选择。因此,我们将这个图中的所有桥的数量求出,记为bridge,那么所有选择中,有桥的方案总数应该为:  bridge*(m-bridge)+bridge*(bridge-1) / 2(即是一条桥和一条非桥的边组成的方案数 + 两条都是桥组成的方案数。)


接下来,我们需要找到两条非桥的边组成的所有方案。具体方法为:

对图进行一遍dfs,得到图的dfs树。

若i为dfs树中的一个节点:

我们定义 T(i) 为以i为根节点的子树种的所有点。

定义P(i)为 i 的所有祖先节点。

定义s(i)为从P(i)中的点到T(i)中的点存在的边的数量。

定义t(i)为T(i)中所有节点能够连接到的i的祖先节点中深度最大的深度。


如上图所示:其中T(3)={ 3 ,4,5 }

                                P(3)={ 2, 1 }

                                s(3)= 3

                                t (3)= 2

在dfs树种,回边只会连向自己的祖先,即不会存在横边的情况。

1.对任意一个节点来说,如果他的s = 1,那么可以断定它和它的父亲的连线段是桥。

2.若节点 i 的 s 值为 2 ,那么可以断定,只要将这两条边减除,就可以将 i 与 i 的祖先分开,因此答案+1。

3.若节点 i 和父亲的边不是桥,并且与父亲节点之间只有一条边,那么可以看出,i的后继中存在与i的祖先相连的边。

                                                     若在 i 的后继中找到某一个节点 j ,它与它的父亲同样只有一条边相连,

                                                                                                                   它的s值与 i 的 s 值相等

                                                                                                                   它的最深回边所到的点是 i 的祖先

                                                       那么,只要减除i与i父亲的边,以及j与j父亲的边,即可形成新的联通分支。如下图所示的橙色边:


将有桥的方案和无桥的方案相加,即可得出总方案数。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int maxn=2005;
struct Edge {
    int a,b;
};
vector<Edge>edges;
vector<int>G[maxn];
int deep[maxn];
int t[maxn];
int s[maxn];
short lowset[maxn][maxn];

void add(int a,int b){
    Edge e;e.a=a;e.b=b;
    edges.push_back(e);
    e.a=b;e.b=a;
    edges.push_back(e);
    G[a].push_back(edges.size()-2);
    G[b].push_back(edges.size()-1);
}

int bridge;
int nobridge;

void init(int n){
    bridge=nobridge=0;
    memset(deep,-1,sizeof(deep));
    memset(t,0,sizeof(t));
    memset(s,0,sizeof(s));
    for(int i=0;i<=n;i++)G[i].clear();
    edges.clear();
}

int check(int u,int fa){
    int cnt=0;
    for(int i=0;i<G[fa].size();i++){
        if(edges[G[fa][i]].b==u)cnt++;
    }
    return cnt;
}
bool Vis[maxn];
void push_down(int u,int su,int du){
    for(int i=0;i<G[u].size();i++){
        int v=edges[G[u][i]].b;
        if(deep[v]!=deep[u]+1)continue;
        if(Vis[v])continue;
        Vis[v]=true;
        if(check(v,u)==1&&su==s[v]&&t[v]<du){
            nobridge++;
        }
        push_down(v,su,du);
    }
}

void dfs(int u,int fa,int No,int dep){
    memset(lowset[u],0,sizeof(lowset[u]));
    deep[u]=dep;
    s[u]=1;
    t[u]=1;
    for(int i=0;i<G[u].size();i++){
        if(G[u][i]==(No^1))continue;
        int v=edges[G[u][i]].b;
        if(deep[v]==-1){
            dfs(v,u,G[u][i],dep+1);
            for(int j=1;j<dep;j++){
                if(lowset[v][j])lowset[u][j]+=lowset[v][j];
            }
        }
        else if(deep[v]<deep[u]){
            lowset[u][deep[v]]++;
        }
    }
    for(int i=1;i<dep;i++){
        if(lowset[u][i]){
            s[u]+=lowset[u][i];
            t[u]=t[u]>i?t[u]:i;
        }
    }
    if(fa==0)s[u]=0;
    if(s[u]==1)        bridge++;
    else if(s[u]==2)   nobridge++;
    if(s[u]!=0&&s[u]!=1&&check(u,fa)==1){
        memset(Vis,0,sizeof(Vis));
        push_down(u,s[u],deep[u]);
    }
}
void read_int(int&x)
{
    char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    x = ch - '0';
    ch=getchar();
    while('0'<=ch&&ch<='9')
    {
        x = 10*x + ch-'0';
        ch = getchar();
    }
}
int main(){
    int n,m,a,b;
    while(scanf("%d%d",&n,&m)!=EOF){
        init(n);
        for(int i=0;i<m;i++){
            read_int(a);
            read_int(b);
            add(a,b);
        }
        dfs(1,0,-1,1);
        int res=bridge*(m-bridge)+(bridge*(bridge-1))/2;
        printf("%d\n",res+nobridge);
    }
    return 0;
}
/*
5 7
1 2
2 3
3 4
3 4
3 4
4 5
5 1
<span style="font-family: Arial, Helvetica, sans-serif;">ans = 6</span>
*/




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值