您的位置:首页 > 其它

hdu 4635 强连通缩点

2014-05-06 10:55 253 查看
/*
题意:给出简单有向图的定义:不能有自环,没有重边,不能强连通;
问给出一个初始图,最多加多少条边可以使得这个图满足上述定义。

题解:强连通缩点
首先没有自环,没有重边都十分清晰,然后不能强连通给了一个提示,就是要找出连通分量,那么怎样才能加最多的边
而使得这个图不是强连通,只需保证将这个图分成两个点集,且其中的一个点集到另一点集只能含有单向的边,然后这
两个点集之内的点是强连通,就保证了最优情况的非强连通,由于有一些边是已存在,因此对于加的边的数目来说要最
优的话,必须找出加边最多的情况;然后需要推出加边的公式:
ans = N - m - a * b
其中N是指整个图为竞赛图时的总边数,然后已经存在的边为m需要减去,然后就是找出a*b,a,b是指将图分成两个点集
之后两个点集的点的数量,理由:分成两个点集之后,一个点集到另一个点集需要的只有单向边,因此要将所有的反向
边都删除,因为是竞赛图,因此所要去除的边的数目即为a*b,减去之后求出的解就是加边数目,而要使得ans最小,则
a*b最小即可;通过强连通缩点将不可能的点集合成一个点,因为从这些点挑选出的部分点集必然会导致分出的两个点集
有回路,因此强连通分量需要选取所有的点,然后得出的一个缩点后的图就是一个拓扑图,我们要选取的应该是a尽量小
的点集,b=(n-a),那么选一个包含点数目最小的点集就是好了,因此应该选择出度或入度为0的点集,因为如果选择的
是出入度均不为0的点集作为a点集,由于有已存在的边不能删除则必然是存在边返回剩余点集,那样不符合要求,因此
需要选择出入度为0的最小点集。
*/
#include <cstdio>
#include <cstring>

#define clr(a,b) (memset(a,b,sizeof(a)))
#define cpy(a,b) (memcpy(a,b,sizeof(a)))
const int NV = 100005;
const int NE = 100005;

inline int Min(int a, int b) {return a < b ? a : b;}
inline int Max(int a, int b) {return a > b ? a : b;}
int deep, scc, top, SZ, n;
struct edge
{int v, next;} E[NE];
int head[NV], dfn[NV], low[NV], id[NV], st[NV];
int indegree[NV],outdegree[NV];
int mindegree[NV],moutdegree[NV];
int sum[NV];
bool in[NV];

inline void init(int _n) {
n = _n;
clr(head, -1);
clr(dfn, -1);
clr(in,false);
deep = scc = SZ = top = 0;
}
void tarjan(int u) {
int v, i;
dfn[u] = low[u] = ++ deep;
st[top++] = u;
in[u] = true;
for (i = head[u]; i != -1; i = E[i].next) {
v = E[i].v;
if (dfn[v] == -1) {
tarjan(v);
low[u] = Min(low[u], low[v]);
}
else if (in[v]) {
low[u] = Min(low[u], dfn[v]);
}
}
if (low[u] == dfn[u]) {
int tsum = 0;
do {
tsum ++;
v = st[--top];
in[v] = false;
id[v] = scc; // 缩点
} while (u != v);
sum[scc] = tsum;
scc ++;
}
}
inline void insert(int u, int v) {
E[SZ].v = v;
E[SZ].next = head[u];
head[u] = SZ ++;
}
inline void solve() {
for (int i = 1; i <= n; i++) {
if (dfn[i] == -1) {
tarjan(i);
}
}
}

int main(void)
{
int t,n,m;
scanf("%d",&t);
for(int cas = 1; cas <= t; cas++)
{
scanf("%d%d",&n,&m);
init(n);
clr(indegree,0);
clr(outdegree,0);
clr(sum,0);
for(int i=0; i<m; i++)
{
int u,v;
scanf("%d%d",&u,&v);
insert(u,v);
}
solve();
if (scc == 1)
{
printf("Case %d: -1\n",cas);
continue;
}

for(int i=1; i<=n; i++)
{
for (int j = head[i]; j != -1; j = E[j].next)
{
if (id[i] != id[E[j].v])
outdegree[id[i]]++,indegree[id[E[j].v]]++;
}
}

int tans = n*n;
for(int i=0; i<scc; i++)
if (indegree[i] == 0 || outdegree[i] == 0)
tans = Min(tans, sum[i]*(n-sum[i]));
printf("Case %d: %d\n",cas,n*(n-1)-m-tans);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: