HDU_2242
这个题目首先可以求出边双连通分量并进行缩点,如果边双连通分量的个数只有一个的话就必然无解,之后我们把缩好的点建成一个新图,这个图实际上就是一棵树。
如果我们随便找个点当做树根,并把图画成树形结构的话,就会发现实际上我们只要求出除树根外每个节点以及其下方的所有节点的权值和即可,每求出一个权值和,就算一下人数差并更新一下min。同时,为了充分利用子问题的解,我们在计算结果的时候可以采用dfs的方式,每个节点返回自己以及自己下方的所有节点的权值和。
需要说明的是,由于这个题目存在两点之间有多条边的情况,于是在用tarjan时就要判断一下当前扫描的边是否是之前来到这个节点的边的反向边。
我之前在codeforces做过一个题目,就是一个tarjan和一个dfs去解,并且由于tarjan本质上就是dfs,于是便用一定手段把两个dfs优化成了一个。想到这里,我又不禁想到,这道题目的tarjan和dfs是否可以优化成一个dfs呢?研究了之后,发现确实可以。
我们可以这么想,最后缩点之后每个点的点权实际上不就是该双连通分量里的所有点的点权和么?而再想想缩点之后dfs计算的过程,实际上是不断计算子问题的解并向上传递值的过程,传递值的过程不就是相当于把所有子节点的值累加给父节点么?
有了这些想法之后,我们每次在找到桥u->v后,在弹栈的过程中,可以把弹出的所有点的点权和算出来,并用这个和更新min,之后再把这个和累加给u即可。这样,tarjan和dfs就合并成了一个dfs的过程了。
/*tarjan + dfs,未优化*/
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define MAXD 10010
#define MAXM 80010
int M, N, people[MAXD];
int gfirst[MAXD], Gfirst[MAXD];
int gnext[MAXM], Gnext[MAXM];
int gv[MAXM], Gv[MAXM], ge, Ge;
int dfn[MAXD], low[MAXD], s[MAXD], top, cnt;
int col, color[MAXD], sum[MAXD], min, total, vis[MAXD];
void Gadd(int x, int y)
{
Gv[Ge] = y;
Gnext[Ge] = Gfirst[x];
Gfirst[x] = Ge;
Ge ++;
}
void gadd(int x, int y)
{
gv[ge] = y;
gnext[ge] = gfirst[x];
gfirst[x] = ge;
ge ++;
}
void init()
{
int i, j, x, y;
for(i = 0; i < N; i ++)
scanf("%d", &people[i]);
memset(Gfirst, -1, sizeof(Gfirst));
Ge = 0;
for(i = 0; i < M; i ++)
{
scanf("%d%d", &x, &y);
Gadd(x, y);
Gadd(y, x);
}
}
void tarjan(int u, int fa)
{
int i, j;
dfn[u] = low[u] = ++ cnt;
s[top ++] = u;
for(i = Gfirst[u]; i != -1; i = Gnext[i])
if(i != (fa ^ 1))
{
if(!dfn[Gv[i]])
{
tarjan(Gv[i], i);
if(low[Gv[i]] < low[u])
low[u] = low[Gv[i]];
else if(low[Gv[i]] > dfn[u])
{
for(s[top] = -1; s[top] != Gv[i];)
{
top --;
color[s[top]] = col;
}
col ++;
}
}
else if(dfn[Gv[i]] < low[u])
low[u] = dfn[Gv[i]];
}
}
int dfs(int u)
{
int i, a = 0, temp;
vis[u] = 1;
for(i = gfirst[u]; i != -1; i = gnext[i])
if(!vis[gv[i]])
{
temp = dfs(gv[i]);
if(abs(total - 2 * temp) < min)
min = abs(total - 2 * temp);
a += temp;
}
return a + sum[u];
}
void solve()
{
int i, j;
cnt = top = 0;
col = 1;
memset(color, 0, sizeof(color));
memset(dfn, 0, sizeof(dfn));
tarjan(0, -1);
if(col == 1)
{
printf("impossible\n");
return ;
}
memset(sum, 0, sizeof(sum));
total = 0;
for(i = 0; i < N; i ++)
{
sum[color[i]] += people[i];
total += people[i];
}
memset(gfirst, -1, sizeof(gfirst));
ge = 0;
for(i = 0; i < N; i ++)
for(j = Gfirst[i]; j != -1; j = Gnext[j])
if(color[i] != color[Gv[j]])
{
int x = color[i], y = color[Gv[j]];
gadd(x, y);
}
min = 0x7fffffff;
memset(vis, 0, sizeof(vis));
dfs(0);
printf("%d\n", min);
}
int main()
{
while(scanf("%d%d", &N, &M) == 2)
{
init();
solve();
}
return 0;
}
/*优化成了一个dfs*/
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define MAXD 10010
#define MAXM 40010
int M, N, people[MAXD];
int first[MAXD], next[MAXM], v[MAXM], e;
int dfn[MAXD], low[MAXD], s[MAXD], top, cnt, min, total, col;
void add(int x, int y)
{
v[e] = y;
next[e] = first[x];
first[x] = e;
e ++;
}
void init()
{
int i, j, x, y;
total = 0;
for(i = 0; i < N; i ++)
{
scanf("%d", &people[i]);
total += people[i];
}
memset(first, -1, sizeof(first));
e = 0;
for(i = 0; i < M; i ++)
{
scanf("%d%d", &x, &y);
add(x, y);
add(y, x);
}
}
void tarjan(int u, int fa)
{
int i, j, temp;
dfn[u] = low[u] = ++ cnt;
s[top ++] = u;
for(i = first[u]; i != -1; i = next[i])
if(i != (fa ^ 1))
{
if(!dfn[v[i]])
{
tarjan(v[i], i);
if(low[v[i]] < low[u])
low[u] = low[v[i]];
else if(low[v[i]] > dfn[u])
{
temp = 0;
for(s[top] = -1; s[top] != v[i];)
{
top --;
temp += people[s[top]];
}
col ++;
if(abs(total - 2 * temp) < min)
min = abs(total - 2 * temp);
people[u] += temp;
}
}
else if(dfn[v[i]] < low[u])
low[u] = dfn[v[i]];
}
}
void solve()
{
int i, j;
cnt = top = 0;
col = 1;
memset(dfn, 0, sizeof(dfn));
min = 0x7fffffff;
tarjan(0, -1);
if(col == 1)
printf("impossible\n");
else
printf("%d\n", min);
}
int main()
{
while(scanf("%d%d", &N, &M) == 2)
{
init();
solve();
}
return 0;
}