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;
}

 

 

posted @ 2013-05-19 17:32  Titanium  阅读(258)  评论(0编辑  收藏  举报