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

这题又是一道混合算法题。

基本思路是用tarjan在无向图中搜出桥边然后缩点将图变成一颗树进行树形DP。

但是这题其实是有平行边的,在双连通tarjan的模板上加一个判断子节点为父亲的语句,让其能通过第二次及以后的边搜到其父亲就能解决平行边问题。

缩点退栈的同时把原来的点所包含的人数全部求出来,求割边的同时也要记录割边两端的节点。

然后根据记录的割边和缩出来的点包含的人数建出新树(其实是图,因为记录的是双向的)。

进行DP时只需要一个DFS从树根(任意选一个缩出来的点)往下搜,每次搜出该子树的人数和,用总人数减去该和的两倍的绝对值就是一个差值,记录最小的那个就是答案了。

注意一点是因为双向建边,DP搜索时要记录父亲,只能往下搜,不能搜该节点的父亲,否则就大错特错。

另外就是没有割边和本身就不是连通图的情况(其实本题一定是连通图)就输出impossible。


附渣代码:

#include <iostream>
#include <cstdio>
#include <vector>
#include <cstdlib>
#define NV 10001
#define NE 20001
using namespace std;
vector<int>orig[NV],newg[NV];
int stack[NV],top;
struct edge{
    int u,v;
}ecut[NE];
int ecnt,bcnt,belong[NV];
int dfn[NV],low[NV];
int dex;
const int W = 0;
const int G = 1;
const int B = 2;
int col[NV];
int dp[NV],ans,sum,val[NV];
int n,m;
int min(int a,int b)
{
    if(a < b)return a;
    return b;
}
void double_tarjan(int u,int f)
{
    col[u] = G;
    stack[top++] = u;
    low[u] = dfn[u] = ++dex;
    bool flag = true;
    for(int i = 0;i < (int)orig[u].size(); i++){
        int v = orig[u][i];
        if(v == f && flag){   //避免平行边。
            flag = false;
            continue;
        }
        if(col[v] == W){
            double_tarjan( v , u );
            low[u] = min( low[u] , low[v] );
            if(low[v] > dfn[u]){
                ecut[++ecnt].u = u;    //保存割边。
                ecut[ecnt].v = v;
            }
        }else if(col[v] == G){
            low[u] = min( low[u] , dfn[v] );
        }
    }
    col[u] = B;
    if(dfn[u] == low[u]){
        int v;
        bcnt++;
        do{
            v = stack[--top];
            belong[v] = bcnt;
            dp[bcnt] += val[v];  //将缩点的原来人数求和。
        }while(u != v);
    }
}
void Dp(int u,int f){
    for(int i = 0;i < (int)newg[u].size(); i++){
        int v = newg[u][i];
        if( v == f ){      //不能往父亲搜回去,只能往下搜。
            continue;
        }
        Dp( v , u );
        dp[u] += dp[v];   //该子树的和。
    }
    int x = abs( sum - (dp[u] << 1) );
    if(x < ans) ans = x; //记录最小答案。
}
void init()
{
    top = 0;
    dex = 0;
    sum = 0;
    ans = 0x3fffffff;
    bcnt = 0;
    ecnt = 0;
    for(int i = 0;i <= n; i++){
        orig[i].clear();
        newg[i].clear();
        dp[i] = 0;
        dfn[i] = 0;
        col[i] = W;
    }
}
int main()
{
    while(scanf("%d%d",&n,&m) != EOF){
        init();
        for(int i = 1;i <= n; i++){
            scanf("%d",val + i);
            sum += val[i];
        }
        for(int i = 1;i <= m; i++){
            int p1,p2;
            scanf("%d%d",&p1,&p2);
            p1++;p2++;             //统一点的编号从1开始。
            orig[p1].push_back(p2);
            orig[p2].push_back(p1);//双向建边。
        }
        int cs = 0;
        for(int i = 1;i <= n; i++){
            if(dfn[i] == 0){
                double_tarjan( i , 0 );
                cs++;             //记录原图是否连通。
            }
        }
        if( ecnt == 0 || cs > 1){//如果割边数目为零或者原图是由多个连通图组成的
            printf("impossible\n");//就输出impossible。
            continue;
        }
        for(int i = 1;i <= ecnt; i++){
            int p1 = belong[ecut[i].u];//将保存的割边和两端所属的缩点建成新的无向图。
            int p2 = belong[ecut[i].v];
            if(p1 != p2){
                newg[p1].push_back(p2);
                newg[p2].push_back(p1);
            }
        }
        Dp( 1 , 0 );                  //进行DP。
        printf("%d\n",ans);
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值