hdu 2242 考研路茫茫——空调教室

双连通分量

边双连通分量+DP  (其实不用DP,直接建树+遍历一次就能计算出全部的DP值)

题意无向图连通,所以只要从一个点运行一次dfs即可,在运行dfs过程中保存下所有的桥并且计算出所有的边双连通分量。在tarjan之后对原图进行缩点,缩点后就能得到一棵,树边刚好就是全部的桥。缩点后每个大点都有一个权值,权值等于 = 属于该连通分量的每个小点的权值和。

因此保存下全部桥是为了方便建树。建树之后对树进行一次遍历(很多人说是DP,其实不算是DP,只是简单的遍历而已)。遍历过程要计算每个节点的dp值,dp[i] = 以点i为节点的子树的所有节点的权值和 

因此每计算完一个节点的dp值后,就可以看看切断这条树边,能不能更新最大的答案, 切断该边的结果为   (SUM - dp[i]) - dp[i]

#include <iostream>

#include <cstdio>

#include <cstring>

#include <utility>

#include <vector>

#include <stack>

using namespace std;

#define N 10010

#define M 20010

#define INF 0x3f3f3f3f



int n,tot,val[N];

int head[N],dfn[N],low[N],belong[N],ins[N],dcnt,bcnt;

int weight[N],dp[N],vis[N],SUM,res;

typedef pair<int ,int> pii;

struct edge

{

    int u,v,used,next;

}e[2*M];

vector<pii>bridge;

stack<int>sta;

vector<int>ver[N];



void add(int u, int v, int k)

{

    e[k].u = u; e[k].v = v; e[k].used = 0;

    e[k].next = head[u]; head[u] = k++;

    u = u^v; v = u^v; u = u^v;

    e[k].u = u; e[k].v = v; e[k].used = 0;

    e[k].next = head[u]; head[u] = k++;

}



void dfs(int u , int fa)

{

    dfn[u] = low[u] = ++dcnt;

    sta.push(u); ins[u] = 1;

    for(int k=head[u]; k!=-1; k=e[k].next)

        if(!e[k].used)

        {

            e[k].used = e[k^1].used = 1;

            int v = e[k].v;

            if(!dfn[v]) //树边

            {

                dfs(v,u);

                low[u] = min(low[u] , low[v]);

                if(low[v] > dfn[u]) //

                {

                    bridge.push_back(make_pair(u,v));

                    while(true)

                    {

                        int x = sta.top();

                        sta.pop(); ins[x] = 0;

                        belong[x] = bcnt;

                        if(x == v) break;

                    }

                    bcnt++; //统计连通分支数

                }

            }

            else if(ins[v]) //后向边

                low[u] = min(low[u] , dfn[v]);

        }

}



inline int abs(int x ,int y)

{

    return x>y? x-y : y-x ;

}



void travel(int u) //遍历整个树,顺便计算出dp值,并且顺便计算出答案

{

    vis[u] = 1;

    dp[u] = weight[u];

    for(int i=0; i<ver[u].size(); i++)

    {

        int v = ver[u][i];

        if(vis[v]) continue;

        travel(v);

        dp[u] += dp[v]; //加上子树的dp值

    }

    res = min(res , abs(SUM-2*dp[u]));

}



void build() //缩点后建树,树边就是桥

{

    SUM = 0;

    res = INF;

    for(int i=0; i<bcnt; i++)

    {

        weight[i] = vis[i] = dp[i] = 0;

        ver[i].clear();

    }

    //计算缩点后每个点的权值

    for(int i=0; i<n; i++)

        weight[belong[i]] += val[i];

    for(int i=0; i<bcnt; i++)

        SUM += weight[i];



    for(int i=0; i<bridge.size(); i++)

    {

        int u = belong[ bridge[i].first ];

        int v = belong[ bridge[i].second ];

        ver[u].push_back(v);

        ver[v].push_back(u);

    }

    travel(0);

//    for(int i=0; i<n; i++) printf("%d[%d]\n",i,belong[i]);

//    for(int i=0; i<bcnt; i++) printf("w=%d\n",weight[i]);

//    for(int i=0; i<bcnt; i++) printf("dp=%d\n",dp[i]);

}



void solve()

{

    dcnt = bcnt = 0;

    bridge.clear();

    while(!sta.empty()) sta.pop();

    memset(ins,0,sizeof(ins));

    memset(dfn,0,sizeof(dfn));

    dfs(0,-1);

    while(!sta.empty())

    {

        int x = sta.top();

        sta.pop(); ins[x] = 0;

        belong[x] = bcnt;

    }

    bcnt++;



    if(bcnt == 1) //整个图就是个边双连通分支,不存在桥

    {

        cout << "impossible" << endl;

        return ;

    }

    

    build(); //建树,并且遍历,遍历过程中就能计算出答案

    cout << res << endl;

}



int main()

{

    while(cin >> n >> tot)

    {

        tot *= 2;

        memset(head,-1,sizeof(head));

        for(int i=0; i<n; i++) cin >> val[i];

        for(int i=0; i<tot; i+=2)

        {

            int u,v;

            cin >> u >> v;

            add(u,v,i);

        }

        solve();

    }

    return 0;

}

 

 

你可能感兴趣的:(HDU)