hdu 2242 考研路茫茫——空调教室 【Tarjan缩点+树上DP】 解题报告

Problem Description

众所周知,HDU的考研教室是没有空调的,于是就苦了不少不去图书馆的考研仔们。Lele也是其中一个。而某教室旁边又摆着两个未装上的空调,更是引起人们无限YY。

一个炎热的下午,Lele照例在教室睡觉的时候,竟然做起了空调教室的美梦。

Lele梦到学校某天终于大发慈悲给某个教室安上了一个空调。而且建造了了M条通气管道,让整个教学楼的全部教室都直接或间接和空调教室连通上,构成了教室群,于是,全部教室都能吹到空调了。

不仅仅这样,学校发现教室人数越来越多,单单一个空调已经不能满足大家的需求。于是,学校决定封闭掉一条通气管道,把全部教室分成两个连通的教室群,再在那个没有空调的教室群里添置一个空调。

当然,为了让效果更好,学校想让这两个教室群里的学生人数尽量平衡。于是学校找到了你,问你封闭哪条通气管道,使得两个教室群的人数尽量平衡,并且输出人数差值的绝对值。

Input

本题目包含多组数据,请处理到文件结束。
每组测试第一行包含两个整数N和M(0

Output

对于每组数据,请在一行里面输出所求的差值。
如果不管封闭哪条管道都不能把教室分成两个教室群,就输出”impossible”。

Sample Input

4 3
1 1 1 1
0 1
1 2
2 3
4 3
1 2 3 5
0 1
1 2
2 3

Sample Output

0
1

思路

我已经不想说了这道题我整了一晚上怕是可以退役了。。。

代码

WA

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<vector>
#include<stack>
using namespace std;
const int N=30000+5;
const int inf=0x3f3f3f3f;
int n,m,peo[N],head[N],num,minn,every,headd[N];
int low[N],dfn[N],tim,cnt,bl[N],val[N],numm,visit[N];
stack<int> s;
struct edge
{
    int u,v,w;
    int next;
}ed[N*2],edd[N*2];
void build(int u,int v)
{
    ed[++num].u=u;
    ed[num].v=v;
    ed[num].w=1;
    ed[num].next=head[u];
    head[u]=num;
}
void add(int u,int v)
{
    edd[++numm].v=v;
    edd[numm].u=u;
    edd[numm].w=1;
    edd[numm].next=headd[u];
    headd[u]=numm;
}
void Tarjan(int u,int father)
{
    dfn[u]=low[u]=tim++;
    visit[u]=1;
    s.push(u);
    int flag=0;
    for (int j=head[u];j!=-1;j=ed[j].next)
    {
        int v=ed[j].v;
        if (v==father&&!flag) {flag=1;continue;}
        if (!visit[v]) Tarjan(v,u);
        low[u]=min(low[u],low[v]);
    }
    if (dfn[u]==low[u])
    {
        cnt++;
        for (int now;now!=u;)
        {
            now=s.top();
            s.pop();
            bl[now]=cnt;
            val[cnt]+=peo[now];
        }
    }
}
void suopoint()
{
    num=0;
    for (int i=1;i<=n;i++)
    for (int j=head[i];j!=-1;j=ed[j].next)
    {
        int v=ed[j].v;
        if (bl[i]!=bl[v]) 
        {
            add(bl[i],bl[v]);
//          add(bl[v],bl[i]);
        }
    }
}
void suo()
{
    for (int i=0;i<num;i++)
    {
        int u=ed[i].u,v=ed[i].v;
        if (bl[u]!=bl[v]) add(u,v);
    }
}
int getmin(int u,int father)
{
    int sum;
    sum=val[u];
    for (int i=headd[u];i!=-1;i=edd[i].next)
    {
        int v=edd[i].v;
        if (v==father) continue;
        sum+=getmin(v,u);
    }
    minn=min(minn,abs(every-2*sum));
    return sum;
}
void clear()
{
    memset(head,-1,sizeof(head));
    memset(headd,-1,sizeof(headd));
    memset(peo,0,sizeof(peo));
    memset(low,0,sizeof(low));
    memset(visit,0,sizeof(visit));
    memset(dfn,0,sizeof(dfn));
    memset(val,0,sizeof(val));
    memset(bl,0,sizeof(bl));
    while(!s.empty()) s.pop();
    num=-1;tim=0;cnt=0;minn=inf;every=0;numm=-1;
}
int main()
{
    freopen ( "in.txt", "r", stdin ) ;
//  freopen ( "out.txt", "w", stdout ) ;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        clear();
        for (int i=0;i<n;i++)
        {
            scanf("%d",&peo[i]);
            every+=peo[i];
        }
        for (int i=1;i<=m;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            build(u,v);
            build(v,u);
        }
//      for (int i=0;i<=n;i++)
//      if (!bl[i]) Tarjan(i,i);
        Tarjan(0,0);
        if (cnt==1) {printf("impossible\n");goto h;}
//      suopoint();
        suo();
        getmin(1,0);
        printf("%d\n",minn);
        h:;
    }
}
/*
4 3
1 1 1 1
0 1
1 2
2 3
4 3
1 2 3 5
0 1
1 2
2 3
3 3
2 1 1
0 1
1 0
1 2
5 6
1 1 1 1 1
0 4
0 1
1 3
1 2
2 4
2 3
2 2
1 1
1 2
2 1
4 5
1 2 3 4
0 1
1 2
2 0
1 0
1 3
4 3
1 2 3 5
0 1
1 2
2 3
4 3
1 1 1 1
0 1
1 2
2 3
*/

AC

# include<stdio.h>
# include<string.h>
# include<stack>
#include<cmath>
using namespace std;
# define N 100005
# define M 200005
struct node{
    int from,to,next;
}edge[2*M],edge1[2*M];
int head[N],tol,n,m,cnt,dfn[N],low[N],visit[N],Belong[N],tol1,head1[N],val[N],val1[N],SUM,Min,count;
stack<int>S;
void add(int a,int b)
{
    edge[tol].from=a;edge[tol].to=b;edge[tol].next=head[a];head[a]=tol++;
}
void add1(int a,int b)
{
    edge1[tol1].from=a;edge1[tol1].to=b;edge1[tol1].next=head1[a];head1[a]=tol1++;
}
int min(int a,int b)
{
    return a<b?a:b;
}
void tarjan(int u,int father)//tarjan找双连通分量并进行缩点
{
    int j,v,flag;
    dfn[u]=low[u]=cnt++;
    visit[u]=1;
    S.push(u);
    flag=0;
    for(j=head[u];j!=-1;j=edge[j].next)
    {
        v=edge[j].to;
        if(v==father && !flag) {flag=1;continue;}//考虑重边的情况,相当重要!
        if(!visit[v]) tarjan(v,u);
        low[u]=min(low[u],low[v]);
    }
    if(dfn[u]==low[u])
    {
        count++;
        do{
            v=S.top();
            S.pop();
            Belong[v]=count;
            val[count]+=val1[v];
        }while(v!=u);
    }//用栈的好处在于使双联通分量的标号连续,如果直接用low[u]来存u的双联通分量,双联通分量的标号不连续,之后再建图的时候不方便
}
int dfs(int u,int father)//树形DP求解最小差值
{
    int j,v,sum;
    sum=val[u];
    for(j=head1[u];j!=-1;j=edge1[j].next)
    {
        v=edge1[j].to;
        if(v==father) continue;
        sum+=dfs(v,u);
    }
    Min=min(Min,abs(SUM-2*sum));
    return sum;
}
int main()
{
    freopen ( "in.txt", "r", stdin ) ;
    freopen ( "out1.txt", "w", stdout ) ;
    int i,a,b;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        memset(head,-1,sizeof(head));
        tol=cnt=0;
        SUM=0;
        for(i=0;i<n;i++)
        {
            scanf("%d",&val1[i]);
            SUM+=val1[i];
        }
        for(i=1;i<=m;i++)
        {
            scanf("%d%d",&a,&b);
            add(a,b);
            add(b,a);
        }
        memset(visit,0,sizeof(visit));
        memset(val,0,sizeof(val));
        count=0;//存双联通分量的个数
        tarjan(0,0);
        if(count==1) {printf("impossible\n");continue;}
        tol1=0;
        memset(head1,-1,sizeof(head1));
        for(i=0;i<tol;i++)
        {
            a=edge[i].from;
            b=edge[i].to;
            if(Belong[a]!=Belong[b]) add1(Belong[a],Belong[b]);
        }
        Min=0xfffffff;
        dfs(1,0);
        printf("%d\n",Min);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值