ID
Origin
Title
  76 / 163 Problem A POJ 1236 Network of Schools
  59 / 177 Problem B UVA 315 Network
  49 / 151 Problem C UVA 796 Critical Links
  28 / 109 Problem D POJ 3694 Network
  39 / 98 Problem E POJ 3177 Redundant Paths
  33 / 230 Problem F HDU 4612 Warm up
  33 / 122 Problem G HDU 4635 Strongly connected
  10 / 42 Problem H HDU 4685 Prince and Princess
  33 / 127 Problem I HDU 4738 Caocao's Bridges
 
 
 
 
 
 
 
 
 
 
 
 
 
 
https://www.byvoid.com/blog/scc-tarjan/
https://www.byvoid.com/blog/biconnect/
 
A.

d.各学校之间有单向的网络,每个学校得到一套软件后,可以通过单向网络向周边的学校传输,

问题1:初始至少需要向多少个学校发放软件,使得网络内所有的学校最终都能得到软件。

问题2:至少需要添加几条传输线路(边),使任意向一个学校发放软件后,经过若干次传送,网络内所有的学校最终都能得到软件。
s.
首先找强连通分量,然后看强连通分量的入度为0点的总数,出度为0点的总数。

第一问为入度为0的强连通分量的个数。

第二问为入度为0的个数和出度为0的个数中大的。(将这个图的所有子树找出来,然后将一棵子树的叶子结点(出度为0)连到另外一棵子树的根结点上(入度为0),这样将所有的叶子结点和根节点全部消掉之后,就可以得到一整个强连通分量,看最少多少条边,这样就是看叶子结点和根节点哪个多,即出度为0和入度为0哪个多。证明?)

c.Tarjan

/*
Tarjan算法
复杂度O(N+M)
*/
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std; const int MAXN=;//点数
const int MAXM=;//边数
struct Edge{
int to,next;
}edge[MAXM];
int head[MAXN],tot;
int Low[MAXN],DFN[MAXN],Stack[MAXN],Belong[MAXN];//Belong数组的值是1~scc
int Index,top;
int scc;//强连通分量的个数
bool Instack[MAXN];
int num[MAXN];//各个强连通分量包含点的个数,数组编号1~scc
//num数组不一定需要,结合实际情况 void addedge(int u,int v){
edge[tot].to=v;
edge[tot].next=head[u];
head[u]=tot++;
}
void Tarjan(int u){
int v;
Low[u]=DFN[u]=++Index;
Stack[top++]=u;
Instack[u]=true;
for(int i=head[u];i!=-;i=edge[i].next){
v=edge[i].to;
if(!DFN[v]){
Tarjan(v);
if(Low[u]>Low[v])Low[u]=Low[v];
}
else if(Instack[v]&&Low[u]>DFN[v])
Low[u]=DFN[v];
}
if(Low[u]==DFN[u]){
scc++;
do{
v=Stack[--top];
Instack[v]=false;
Belong[v]=scc;
num[scc]++;
}
while(v!=u);
}
}
void solve(int N){
memset(DFN,,sizeof(DFN));
memset(Instack,false,sizeof(Instack));
memset(num,,sizeof(num));
Index=scc=top=;
for(int i=;i<=N;i++)
if(!DFN[i])
Tarjan(i);
}
void init(){
tot=;
memset(head,-,sizeof(head));
} int main(){
int N;
int v; int indegree[MAXN],outdegree[MAXN];//强连通分量的入度,强连通分量的出度
int in0,out0;//入度为0的强连通分量个数,出度为0的... while(~scanf("%d",&N)){
init();
memset(indegree,,sizeof(indegree));
memset(outdegree,,sizeof(outdegree));
in0=out0=; for(int u=;u<=N;++u){
while(scanf("%d",&v)){
if(v==)break;
addedge(u,v);
}
} solve(N); if(scc==)printf("1\n0\n");//只有一个强连通 分量
else{
//枚举所有的边,统计强连通分量的入度、出度
for(int i=;i<=N;++i){
for(int j=head[i];j!=-;j=edge[j].next){
if(Belong[i]!=Belong[edge[j].to]){
++indegree[Belong[edge[j].to]];
++outdegree[Belong[i]];
}
}
}
//强连通分量的入度为0的个数,出度为0的个数
for(int i=;i<=scc;++i){
if(indegree[i]==)++in0;
if(outdegree[i]==)++out0;
} if(in0>out0)printf("%d\n%d\n",in0,in0);
else printf("%d\n%d\n",in0,out0);
}
}
return ;
}

B.

d.求割点数目

c.这里输入用了strtok的技巧

#include <stdio.h>
#include <algorithm>
#include <iostream>
#include <string.h>
using namespace std; /*
* 求 无向图的割点和桥
* 可以找出割点和桥,求删掉每个点后增加的连通块。
* 需要注意重边的处理,可以先用矩阵存,再转邻接表,或者进行判重
*/
const int MAXN = ;
const int MAXM = ;
struct Edge
{
int to,next;
bool cut;//是否为桥的标记
}edge[MAXM];
int head[MAXN],tot;
int Low[MAXN],DFN[MAXN],Stack[MAXN];
int Index,top;
bool Instack[MAXN];
bool cut[MAXN];
int add_block[MAXN];//删除一个点后增加的连通块
int bridge; void addedge(int u,int v)
{
edge[tot].to = v;edge[tot].next = head[u];edge[tot].cut = false;
head[u] = tot++;
} void Tarjan(int u,int pre)
{
int v;
Low[u] = DFN[u] = ++Index;
Stack[top++] = u;
Instack[u] = true;
int son = ;
for(int i = head[u];i != -;i = edge[i].next)
{
v = edge[i].to;
if(v == pre)continue;
if( !DFN[v] )
{
son++;
Tarjan(v,u);
if(Low[u] > Low[v])Low[u] = Low[v];
//桥
//一条无向边(u,v)是桥,当且仅当(u,v)为树枝边,且满足DFS(u)<Low(v)。
if(Low[v] > DFN[u])
{
bridge++;
edge[i].cut = true;
edge[i^].cut = true;
}
//割点
//一个顶点u是割点,当且仅当满足(1)或(2) (1) u为树根,且u有多于一个子树。
//(2) u不为树根,且满足存在(u,v)为树枝边(或称父子边,
//即u为v在搜索树中的父亲),使得DFS(u)<=Low(v)
if(u != pre && Low[v] >= DFN[u])//不是树根
{
cut[u] = true;
add_block[u]++;
}
}
else if( Low[u] > DFN[v])
Low[u] = DFN[v];
}
//树根,分支数大于1
if(u == pre && son > )cut[u] = true;
if(u == pre)add_block[u] = son - ;
Instack[u] = false;
top--;
} void solve(int N)
{
memset(DFN,,sizeof(DFN));
memset(Instack,false,sizeof(Instack));
memset(add_block,,sizeof(add_block));
memset(cut,false,sizeof(cut));
Index = top = ;
bridge = ;
for(int i = ;i <= N;i++)
if(!DFN[i])
Tarjan(i,i);
int ans = ;
for(int i = ;i <= N;i++)
if(cut[i])
ans++;
printf("%d\n",ans);
}
void init()
{
tot = ;
memset(head,-,sizeof(head));
}
int g[][];
char buf[];
int main()
{
int n;
while(scanf("%d",&n)== && n)
{
gets(buf);
memset(g,,sizeof(g));
while(gets(buf))
{
if(strcmp(buf,"")==)break;
char *p = strtok(buf," ");
int u;
sscanf(p,"%d",&u);
p = strtok(NULL," ");
int v;
while(p)
{
sscanf(p,"%d",&v);
p = strtok(NULL," ");
g[u][v]=g[v][u]=;
}
}
init();
for(int i = ;i <= n;i++)
for(int j = i+;j <= n;j++)
if(g[i][j])
{
addedge(i,j);
addedge(j,i);
}
solve(n);
}
return ;
}

C.

d.求桥的数目,并按顺序输出桥

c.

#include <stdio.h>
#include <iostream>
#include <string.h>
#include <algorithm>
#include <map>
#include <vector>
using namespace std;
/*
* 求 无向图的割点和桥
* 可以找出割点和桥,求删掉每个点后增加的连通块。
* 需要注意重边的处理,可以先用矩阵存,再转邻接表,或者进行判重
*/
const int MAXN = ;
const int MAXM = ;
struct Edge
{
int to,next;
bool cut;//是否为桥的标记
}edge[MAXM];
int head[MAXN],tot;
int Low[MAXN],DFN[MAXN],Stack[MAXN];
int Index,top;
bool Instack[MAXN];
bool cut[MAXN];
int add_block[MAXN];//删除一个点后增加的连通块
int bridge; void addedge(int u,int v)
{
edge[tot].to = v;edge[tot].next = head[u];edge[tot].cut = false;
head[u] = tot++;
} void Tarjan(int u,int pre)
{
int v;
Low[u] = DFN[u] = ++Index;
Stack[top++] = u;
Instack[u] = true;
int son = ;
for(int i = head[u];i != -;i = edge[i].next)
{
v = edge[i].to;
if(v == pre)continue;
if( !DFN[v] )
{
son++;
Tarjan(v,u);
if(Low[u] > Low[v])Low[u] = Low[v];
//桥
//一条无向边(u,v)是桥,当且仅当(u,v)为树枝边,且满足DFS(u)<Low(v)。
if(Low[v] > DFN[u])
{
bridge++;
edge[i].cut = true;
edge[i^].cut = true;
}
//割点
//一个顶点u是割点,当且仅当满足(1)或(2) (1) u为树根,且u有多于一个子树。
//(2) u不为树根,且满足存在(u,v)为树枝边(或称父子边,
//即u为v在搜索树中的父亲),使得DFS(u)<=Low(v)
if(u != pre && Low[v] >= DFN[u])//不是树根
{
cut[u] = true;
add_block[u]++;
}
}
else if( Low[u] > DFN[v])
Low[u] = DFN[v];
}
//树根,分支数大于1
if(u == pre && son > )cut[u] = true;
if(u == pre)add_block[u] = son - ;
Instack[u] = false;
top--;
} void solve(int N)
{
memset(DFN,,sizeof(DFN));
memset(Instack,false,sizeof(Instack));
memset(add_block,,sizeof(add_block));
memset(cut,false,sizeof(cut));
Index = top = ;
bridge = ;
for(int i = ;i <= N;i++)
if( !DFN[i] )
Tarjan(i,i);
printf("%d critical links\n",bridge);
vector<pair<int,int> >ans;
for(int u = ;u <= N;u++)
for(int i = head[u];i != -;i = edge[i].next)
if(edge[i].cut && edge[i].to > u)
{
ans.push_back(make_pair(u,edge[i].to));
}
sort(ans.begin(),ans.end());
//按顺序输出桥
for(int i = ;i < ans.size();i++)
printf("%d - %d\n",ans[i].first-,ans[i].second-);
printf("\n");
}
void init()
{
tot = ;
memset(head,-,sizeof(head));
}
//处理重边
map<int,int>mapit;
inline bool isHash(int u,int v)
{
if(mapit[u*MAXN+v])return true;
if(mapit[v*MAXN+u])return true;
mapit[u*MAXN+v] = mapit[v*MAXN+u] = ;
return false;
}
int main()
{
int n;
while(scanf("%d",&n) == )
{
init();
int u;
int k;
int v;
//mapit.clear();
for(int i = ;i <= n;i++)
{
scanf("%d (%d)",&u,&k);
u++;
//这样加边,要保证正边和反边是相邻的,建无向图
while(k--)
{
scanf("%d",&v);
v++;
if(v <= u)continue;
//if(isHash(u,v))continue;
addedge(u,v);
addedge(v,u);
}
}
solve(n);
}
return ;
}

D.

这题是给了一个连通图。
问再加入边的过程中,桥的个数。
先对原图进行双连通分支缩点。可以形成一颗树。
这颗树的边都是桥。
然后加入边以后,查询LCA,LCA上的桥都减掉。
标记边为桥不方便,直接标记桥的终点就可以了。
具体看代码吧!
很好的题目
#include <stdio.h>
#include <iostream>
#include <algorithm>
#include <string.h>
#include <queue>
#include <vector>
using namespace std; const int MAXN = ;
const int MAXM = ; struct Edge
{
int to,next;
bool cut;
}edge[MAXM];
int head[MAXN],tot;
int Low[MAXN],DFN[MAXN],Stack[MAXN],Belong[MAXN];//Belong数组的值是1~block
int Index,top;
int block;
bool Instack[MAXN];
int bridge; void addedge(int u,int v)
{
edge[tot].to = v;edge[tot].next = head[u];edge[tot].cut = false;
head[u] = tot++;
}
void Tarjan(int u,int pre)
{
int v;
Low[u] = DFN[u] = ++Index;
Stack[top++] = u;
Instack[u] = true;
for(int i = head[u];i != -;i = edge[i].next)
{
v = edge[i].to;
if( v == pre )continue;
if( !DFN[v] )
{
Tarjan(v,u);
if(Low[u] > Low[v])Low[u] = Low[v];
if(Low[v] > Low[u])
{
bridge++;
edge[i].cut = true;
edge[i^].cut = true;
}
}
else if(Instack[v] && Low[u] > DFN[v])
Low[u] = DFN[v];
}
if(Low[u] == DFN[u])
{
block++;
do
{
v = Stack[--top];
Instack[v] = false;
Belong[v] = block;
}
while( v != u );
}
}
void init()
{
tot = ;
memset(head,-,sizeof(head));
} vector<int>vec[MAXN];
int father[MAXN];
int dep[MAXN];
int a[MAXN];
void lca_bfs(int root)
{
memset(dep,-,sizeof(dep));
dep[root] = ;
a[root] = ;//桥的标记,标记桥的一个点
father[root] = -;
queue<int>q;
q.push(root);
while(!q.empty())
{
int tmp = q.front();
q.pop();
for(int i = ;i < vec[tmp].size();i++)
{
int v = vec[tmp][i];
if(dep[v]!=-)continue;
dep[v] = dep[tmp]+;
a[v] = ;
father[v] = tmp;
q.push(v);
}
}
}
int ans;
void lca(int u,int v)
{
if(dep[u]>dep[v])swap(u,v);
while(dep[u]<dep[v])
{
if(a[v])
{
ans--;
a[v] = ;
}
v = father[v];
}
while(u != v)
{
if(a[u])
{
ans--;
a[u] = ;
}
if(a[v])
{
ans--;
a[v] = ;
}
u = father[u];
v = father[v];
}
}
void solve(int N)
{
memset(DFN,,sizeof(DFN));
memset(Instack,false,sizeof(Instack));
Index = top = block = ;
Tarjan(,);
for(int i = ;i <= block;i++)
vec[i].clear();
for(int u = ;u <= N;u++)
for(int i = head[u];i != -;i = edge[i].next)
if(edge[i].cut)
{
int v = edge[i].to;
vec[Belong[u]].push_back(Belong[v]);
vec[Belong[v]].push_back(Belong[u]);
}
lca_bfs();
ans = block - ;
int Q;
int u,v;
scanf("%d",&Q);
while(Q--)
{
scanf("%d%d",&u,&v);
lca(Belong[u],Belong[v]);
printf("%d\n",ans);
}
printf("\n");
}
int main()
{
int n,m;
int u,v;
int iCase = ;
while(scanf("%d%d",&n,&m)==)
{
iCase++;
if(n== && m == )break;
init();
while(m--)
{
scanf("%d%d",&u,&v);
addedge(u,v);
addedge(v,u);
}
printf("Case %d:\n",iCase);
solve(n);
}
return ;
}

E.

就是给了一个连通图,问加多少条边可以变成边双连通。

去掉桥,其余的连通分支就是边双连通分支了。一个有桥的连通图要变成边双连通图的话,把双连通子图收缩为一个点,形成一颗树。需要加的边为(leaf+1)/2    (leaf为叶子结点个数)

POJ 3177 给定一个连通的无向图G,至少要添加几条边,才能使其变为双连通图。

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <map>
using namespace std; const int MAXN = ;//点数
const int MAXM = ;//边数,因为是无向图,所以这个值要*2 struct Edge
{
int to,next;
bool cut;//是否是桥标记
}edge[MAXM];
int head[MAXN],tot;
int Low[MAXN],DFN[MAXN],Stack[MAXN],Belong[MAXN];//Belong数组的值是1~block
int Index,top;
int block;//边双连通块数
bool Instack[MAXN];
int bridge;//桥的数目 void addedge(int u,int v)
{
edge[tot].to = v;edge[tot].next = head[u];edge[tot].cut=false;
head[u] = tot++;
} void Tarjan(int u,int pre)
{
int v;
Low[u] = DFN[u] = ++Index;
Stack[top++] = u;
Instack[u] = true;
for(int i = head[u];i != -;i = edge[i].next)
{
v = edge[i].to;
if(v == pre)continue;
if( !DFN[v] )
{
Tarjan(v,u);
if( Low[u] > Low[v] )Low[u] = Low[v];
if(Low[v] > DFN[u])
{
bridge++;
edge[i].cut = true;
edge[i^].cut = true;
}
}
else if( Instack[v] && Low[u] > DFN[v] )
Low[u] = DFN[v];
}
if(Low[u] == DFN[u])
{
block++;
do
{
v = Stack[--top];
Instack[v] = false;
Belong[v] = block;
}
while( v!=u );
}
}
void init()
{
tot = ;
memset(head,-,sizeof(head));
} int du[MAXN];//缩点后形成树,每个点的度数
void solve(int n)
{
memset(DFN,,sizeof(DFN));
memset(Instack,false,sizeof(Instack));
Index = top = block = ;
Tarjan(,);
int ans = ;
memset(du,,sizeof(du));
for(int i = ;i <= n;i++)
for(int j = head[i];j != -;j = edge[j].next)
if(edge[j].cut)
du[Belong[i]]++;
for(int i = ;i <= block;i++)
if(du[i]==)
ans++;
//找叶子结点的个数ans,构造边双连通图需要加边(ans+1)/2
printf("%d\n",(ans+)/);
}
int main()
{
int n,m;
int u,v;
while(scanf("%d%d",&n,&m)==)
{
init();
while(m--)
{
scanf("%d%d",&u,&v);
addedge(u,v);
addedge(v,u);
}
solve(n);
}
return ;
}

F.

问加一条边,最少可以剩下几个桥。

先双连通分量缩点,形成一颗树,然后求树的直径,就是减少的桥。

本题要处理重边的情况。

如果本来就两条重边,不能算是桥。

还会爆栈,只能C++交,手动加栈了

#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <map>
#include <vector>
using namespace std; const int MAXN = ;//点数
const int MAXM = ;//边数,因为是无向图,所以这个值要*2 struct Edge
{
int to,next;
bool cut;//是否是桥标记
bool cong;
}edge[MAXM];
int head[MAXN],tot;
int Low[MAXN],DFN[MAXN],Stack[MAXN],Belong[MAXN];//Belong数组的值是1~block
int Index,top;
int block;//边双连通块数
bool Instack[MAXN];
int bridge;//桥的数目 void addedge(int u,int v,bool pp)
{
edge[tot].to = v;edge[tot].next = head[u];edge[tot].cut=false;
edge[tot].cong = pp;
head[u] = tot++;
} void Tarjan(int u,int pre,bool ff)
{
int v;
Low[u] = DFN[u] = ++Index;
Stack[top++] = u;
Instack[u] = true;
for(int i = head[u];i != -;i = edge[i].next)
{
v = edge[i].to;
if(v == pre && (!ff))continue;
if( !DFN[v] )
{
Tarjan(v,u,edge[i].cong);
if( Low[u] > Low[v] )Low[u] = Low[v];
if(Low[v] > DFN[u])
{
bridge++;
edge[i].cut = true;
edge[i^].cut = true;
}
}
else if( Instack[v] && Low[u] > DFN[v] )
Low[u] = DFN[v];
}
if(Low[u] == DFN[u])
{
block++;
do
{
v = Stack[--top];
Instack[v] = false;
Belong[v] = block;
}
while( v!=u );
}
}
void init()
{
tot = ;
memset(head,-,sizeof(head));
} int du[MAXN];//缩点后形成树,每个点的度数
vector<int>vec[MAXN];
int dep[MAXN];
void dfs(int u)
{
for(int i = ;i < vec[u].size();i++)
{
int v = vec[u][i];
if(dep[v]!=-)continue;
dep[v]=dep[u]+;
dfs(v);
}
}
void solve(int n)
{
memset(DFN,,sizeof(DFN));
memset(Instack,false,sizeof(Instack));
Index = top = block = ;
Tarjan(,,false);
for(int i = ;i <= block;i++)
vec[i].clear();
for(int i = ;i <= n;i++)
for(int j = head[i];j != -;j = edge[j].next)
if(edge[j].cut)
{
vec[Belong[i]].push_back(Belong[edge[j].to]);
}
memset(dep,-,sizeof(dep));
dep[]=;
dfs();
int k = ;
for(int i = ;i <= block;i++)
if(dep[i]>dep[k])
k = i;
memset(dep,-,sizeof(dep));
dep[k]=;
dfs(k);
int ans = ;
for(int i = ;i <= block;i++)
ans = max(ans,dep[i]);
printf("%d\n",block--ans);
}
struct NN
{
int u,v;
}node[MAXM];
bool cmp(NN a,NN b)
{
if(a.u != b.u)return a.u<b.u;
else return a.v<b.v;
}
int main()
{
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
int n,m;
int u,v;
while(scanf("%d%d",&n,&m)==)
{
if(n== && m==)break;
init();
for(int i = ;i < m;i++)
{
scanf("%d%d",&u,&v);
if(u==v)continue;
if(u>v)swap(u,v);
node[i].u = u;
node[i].v = v;
}
sort(node,node+m,cmp);
for(int i = ;i < m;i++)
{
if(i == || (node[i].u!=node[i-].u || node[i].v != node[i-].v))
{
if(i < m- && (node[i].u==node[i+].u && node[i].v == node[i+].v))
{
addedge(node[i].u,node[i].v,true);
addedge(node[i].v,node[i].u,true);
}
else
{
addedge(node[i].u,node[i].v,false);
addedge(node[i].v,node[i].u,false);
}
}
}
solve(n);
}
return ;
}

G.

不太懂,最后应该也不是找点最少的强连通分量,好像全都试了一遍。

求最多可以加多少边,使得最新的图不是强连通图
最终情况一定是再加一条边,整张图就是强连通的了,那么我们可以把图看成2部分x和y,x和y都是完全图,然后x每个点到y每个点都有边,但是y到x没有边,如果有肯定是强连通了,设x中有a个点,y中有b个点 则 a + b = n
则边数就是 a * (a - 1) + b * (b - 1) + a * b - m,化简得到:n * n - a * b - n - m;
如何让这个值大那就是如何选取x和y的问题了,显然a和b差距越大越好,那么就可以通过tarajan来找出一个包含点最少的强连通分量来当x,其他的强连通分量作为y,这样就很容易了

Tarjan 缩点。

/*
* Author:kuangbin
* 1004.cpp
*/ #include <stdio.h>
#include <algorithm>
#include <string.h>
#include <iostream>
#include <map>
#include <vector>
#include <queue>
#include <set>
#include <string>
#include <math.h>
using namespace std;
/*
* Tarjan算法
* 复杂度O(N+M)
*/
const int MAXN = ;//点数
const int MAXM = ;//边数
struct Edge
{
int to,next;
}edge[MAXM];
int head[MAXN],tot;
int Low[MAXN],DFN[MAXN],Stack[MAXN],Belong[MAXN];//Belong数组的值是1~scc
int Index,top;
int scc;//强连通分量的个数
bool Instack[MAXN];
int num[MAXN];//各个强连通分量包含点的个数,数组编号1~scc
//num数组不一定需要,结合实际情况 void addedge(int u,int v)
{
edge[tot].to = v;edge[tot].next = head[u];head[u] = tot++;
}
void Tarjan(int u)
{
int v;
Low[u] = DFN[u] = ++Index;
Stack[top++] = u;
Instack[u] = true;
for(int i = head[u];i != -;i = edge[i].next)
{
v = edge[i].to;
if( !DFN[v] )
{
Tarjan(v);
if( Low[u] > Low[v] )Low[u] = Low[v];
}
else if(Instack[v] && Low[u] > DFN[v])
Low[u] = DFN[v];
}
if(Low[u] == DFN[u])
{
scc++;
do
{
v = Stack[--top];
Instack[v] = false;
Belong[v] = scc;
num[scc]++;
}
while( v != u);
}
}
void solve(int N)
{
memset(DFN,,sizeof(DFN));
memset(Instack,false,sizeof(Instack));
memset(num,,sizeof(num));
Index = scc = top = ;
for(int i = ;i <= N;i++)
if(!DFN[i])
Tarjan(i);
}
void init()
{
tot = ;
memset(head,-,sizeof(head));
}
int in[MAXN],out[MAXN];
int main()
{
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
int T;
scanf("%d",&T);
int iCase = ;
int n,m;
int u,v;
while(T--)
{
iCase++;
init();
scanf("%d%d",&n,&m);
for(int i = ;i < m;i++)
{
scanf("%d%d",&u,&v);
addedge(u,v);
}
solve(n);
if(scc == )
{
printf("Case %d: -1\n",iCase);
continue;
}
for(int i = ;i <= scc;i++)
{
in[i] = ;
out[i] = ;
}
for(int u = ;u <= n;u++)
for(int i = head[u];i != -;i = edge[i].next)
{
int v = edge[i].to;
if(Belong[u]==Belong[v])continue;
out[Belong[u]]++;
in[Belong[v]]++;
}
long long sss = (long long)n*(n-) - m;
long long ans = ;
for(int i = ;i <= scc;i++)
{
if(in[i]== || out[i] == )
ans = max(ans,sss - (long long)num[i]*(n-num[i]));
}
printf("Case %d: %d\n",iCase,ans);
}
return ;
}

H.

比较难想的题。

主要构图转化的思路很神奇,我看了题解才会的,T_T

首先n,m个点做一次二分匹配,得到匹配数最大为res.   相当于右边有m-res个点没有匹配,左边有n-res个点没有匹配。

所以在左加m-res个点,和右边所有相连。

在右边加n-res个点,个左边所有相连。

然后做n+m-res,n+m-res个点的二分匹配。(ps:其实不用再二分匹配了,手工添加剩下的就行,时间会快1s)

匹配数肯定是n+m-res;

主要是得到匹配的情况。

对于左边的点i.  把i相匹配的在右边的点,向其余和i相连的点连一有向边。

然后做强连通缩点。

如果边u-v. v和u匹配的点在一个连通分支,说明可以交换,可以u->v组合,不影响最大匹配数

/* ***********************************************
Author :kuangbin
Created Time :2013/8/15 23:20:43
File Name :F:\2013ACM练习\2013多校8\1010.cpp
************************************************ */ #include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <math.h>
#include <stdlib.h>
#include <time.h>
using namespace std;
const int MAXN = ;
int uN,vN;
int g[MAXN][MAXN];
int linker[MAXN];
bool used[MAXN];
bool dfs(int u)
{
for(int v = ; v <= vN;v++)
if(g[u][v] && !used[v])
{
used[v] = true;
if(linker[v] == - || dfs(linker[v]))
{
linker[v] = u;
return true;
}
}
return false;
}
int hungary()
{
int res = ;
memset(linker,-,sizeof(linker));
for(int u = ; u <= uN;u++)
{
memset(used,false,sizeof(used));
if(dfs(u))res++;
}
return res;
}
int lx[MAXN];
const int MAXM = ;//边数
struct Edge
{
int to,next;
}edge[MAXM];
int head[MAXN],tot;
int Low[MAXN],DFN[MAXN],Stack[MAXN],Belong[MAXN];//Belong数组的值是1~scc
int Index,top;
int scc;//强连通分量的个数
bool Instack[MAXN];
int num[MAXN];//各个强连通分量包含点的个数,数组编号1~scc
//num数组不一定需要,结合实际情况 void addedge(int u,int v)
{
edge[tot].to = v;edge[tot].next = head[u];head[u] = tot++;
}
void Tarjan(int u)
{
int v;
Low[u] = DFN[u] = ++Index;
Stack[top++] = u;
Instack[u] = true;
for(int i = head[u];i != -;i = edge[i].next)
{
v = edge[i].to;
if( !DFN[v] )
{
Tarjan(v);
if( Low[u] > Low[v] )Low[u] = Low[v];
}
else if(Instack[v] && Low[u] > DFN[v])
Low[u] = DFN[v];
}
if(Low[u] == DFN[u])
{
scc++;
do
{
v = Stack[--top];
Instack[v] = false;
Belong[v] = scc;
num[scc]++;
}
while( v != u);
}
}
void solve(int N)
{
memset(DFN,,sizeof(DFN));
memset(Instack,false,sizeof(Instack));
memset(num,,sizeof(num));
Index = scc = top = ;
for(int i = ;i <= N;i++)
if(!DFN[i])
Tarjan(i);
}
void init()
{
tot = ;
memset(head,-,sizeof(head));
} int main()
{
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
int n,m;
int k;
int T;
scanf("%d",&T);
int iCase = ;
while(T--)
{
iCase++;
scanf("%d%d",&n,&m);
memset(g,,sizeof(g));
int v;
for(int i = ;i <= n;i++)
{
scanf("%d",&k);
while(k--)
{
scanf("%d",&v);
g[i][v] = ;
}
}
uN = n;
vN = m;
int res = hungary();
uN = vN = n + m - res;
for(int i = n+;i <= uN;i++)
for(int j = ;j <= vN;j++)
g[i][j] = ;
for(int i = ;i <= uN;i++)
for(int j = m+;j <= vN;j++)
g[i][j] = ;
hungary();
memset(lx,-,sizeof(lx));
for(int i = ;i <= vN;i++)
if(linker[i] != -)
lx[linker[i]] = i;
init();
for(int i = ;i <= uN;i++)
for(int j = ;j <= vN;j++)
if(g[i][j] && j != lx[i])
addedge(lx[i],j);
solve(vN);
printf("Case #%d:\n",iCase);
vector<int>ans;
for(int i = ;i <= n;i++)
{
ans.clear();
for(int j = ; j <= m;j++)
if(g[i][j] && Belong[j] == Belong[lx[i]])
ans.push_back(j);
int sz = ans.size();
printf("%d",sz);
for(int i = ;i < sz;i++)
printf(" %d",ans[i]);
printf("\n");
}
}
return ;
}

I.

这题的意思就是求出所有的桥,然后输出桥的权值的最小值。

但是坑点比较多。

如果一开始是不连通的,输出0.

图有重边,需要处理。

还有如果取到的最小值是0的话,要输出1,表示要派一个人过去。

/* ***********************************************
Author :kuangbin
Created Time :2013/9/15 星期日 12:11:49
File Name :2013杭州网络赛\1001.cpp
************************************************ */ #pragma comment(linker, "/STACK:1024000000,1024000000")
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <math.h>
#include <stdlib.h>
#include <time.h>
using namespace std;
const int INF = 0x3f3f3f3f;
/*
* 求 无向图的割点和桥
* 可以找出割点和桥,求删掉每个点后增加的连通块。
* 需要注意重边的处理,可以先用矩阵存,再转邻接表,或者进行判重
*/
const int MAXN = ;
const int MAXM = ;
struct Edge
{
int to,next;
int w;
bool cut;//是否为桥的标记
}edge[MAXM];
int head[MAXN],tot;
int Low[MAXN],DFN[MAXN],Stack[MAXN];
int Index,top;
bool Instack[MAXN];
bool cut[MAXN];
int add_block[MAXN];//删除一个点后增加的连通块
int bridge; void addedge(int u,int v,int w)
{
edge[tot].to = v;edge[tot].next = head[u];edge[tot].cut = false;
edge[tot].w = w;
head[u] = tot++;
} void Tarjan(int u,int pre)
{
int v;
Low[u] = DFN[u] = ++Index;
Stack[top++] = u;
Instack[u] = true;
int son = ;
int pre_num = ;
for(int i = head[u];i != -;i = edge[i].next)
{
v = edge[i].to;
if(v == pre && pre_num == )
{
pre_num++;
continue; }
if( !DFN[v] )
{
son++;
Tarjan(v,u);
if(Low[u] > Low[v])Low[u] = Low[v];
//桥
//一条无向边(u,v)是桥,当且仅当(u,v)为树枝边,且满足DFS(u)<Low(v)。
if(Low[v] > DFN[u])
{
bridge++;
edge[i].cut = true;
edge[i^].cut = true;
}
//割点
//一个顶点u是割点,当且仅当满足(1)或(2) (1) u为树根,且u有多于一个子树。
//(2) u不为树根,且满足存在(u,v)为树枝边(或称父子边,
//即u为v在搜索树中的父亲),使得DFS(u)<=Low(v)
if(u != pre && Low[v] >= DFN[u])//不是树根
{
cut[u] = true;
add_block[u]++;
}
}
else if( Low[u] > DFN[v])
Low[u] = DFN[v];
}
//树根,分支数大于1
if(u == pre && son > )cut[u] = true;
if(u == pre)add_block[u] = son - ;
Instack[u] = false;
top--;
}
int solve(int N)
{
memset(DFN,,sizeof(DFN));
memset(Instack,false,sizeof(Instack));
memset(add_block,,sizeof(add_block));
memset(cut,false,sizeof(cut));
Index = top = ;
bridge = ;
for(int i = ;i <= N;i++)
if( !DFN[i] )
Tarjan(i,i);
int ret = INF;
for(int u = ; u <= N;u++)
for(int i = head[u]; i != -;i = edge[i].next)
if(edge[i].cut)
ret = min(ret,edge[i].w);
if(ret == INF)ret = -;
if(ret == )ret++;
return ret;
}
int F[MAXN];
int find(int x)
{
if(F[x] == -)return x;
else return F[x] = find(F[x]);
}
void init()
{
memset(F,-,sizeof(F));
tot = ;
memset(head,-,sizeof(head));
}
void bing(int u,int v)
{
int t1 = find(u);
int t2 = find(v);
if(t1 != t2)F[t1] = t2;
}
int main()
{
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
int n,m;
while(scanf("%d%d",&n,&m) == )
{
if(n == && m == )break;
int u,v,w;
init();
while(m--)
{
scanf("%d%d%d",&u,&v,&w);
if(u == v)continue;
addedge(u,v,w);
addedge(v,u,w);
bing(u,v);
}
bool flag = true;
for(int i = ; i <= n;i++)
if(find(i) != find())
flag = false;
if(!flag)
{
printf("0\n");
continue;
}
printf("%d\n",solve(n));
}
return ;
}

[kuangbin带你飞]专题九 连通图的更多相关文章

  1. [kuangbin带你飞]专题九 连通图E POJ 3177 Redundant Paths

    这个题最开始我想的是,直接缩点求双连通分量,连接这些双联通分量不就行了吗? 但是其实是不对的,双连通内部双联通,我们如果任意的连接一条边在这些双联通分量之间,他们之间有没有桥其实并不知道. 我应该是求 ...

  2. [kuangbin带你飞]专题九 连通图D - Network POJ - 3694

    这道题其实也非常简单,只是在求割边及其个数的情况下,每次往里面加入新的边,并再次计算割边的个数. 我们用tarjan可以求出原图的桥以及个数,当然我们不能暴力加边,然后求解,那么如何求呢??? 其实非 ...

  3. [kuangbin带你飞]专题九 连通图C - Critical Links UVA - 796

    这道题就是要求桥的个数. 那么桥相应的也有判定的定理: 在和u相邻的节点中,存在一个节点是最小的时间戳都比 当前u的访问次序要大,也就是说这个点是只能通过果u到达,那么 他们之间相邻的边就是的桥 #i ...

  4. [kuangbin带你飞]专题九 连通图B - Network UVA - 315

    判断割点的性质: 如果点y满足 low[y]>=dfn[x] 且不是根节点 或者是根节点,满足上述式子的有两个及其以上. 就是割点 如果是起点,那么至少需要两个子节点满足上述条件,因为它是根节点 ...

  5. 给自己的小练习19-[kuangbin带你飞]专题九连通图

    没有写题解.补一波 Network of Schools 问题1:求有向图中入度为0的点个数 问题2:使得整个图变成一个联通分量 问题1直接缩点统计 问题2=max(入度为0的点,出度为0的点),注意 ...

  6. [kuangbin带你飞]专题1-23题目清单总结

    [kuangbin带你飞]专题1-23 专题一 简单搜索 POJ 1321 棋盘问题POJ 2251 Dungeon MasterPOJ 3278 Catch That CowPOJ 3279 Fli ...

  7. 【算法系列学习三】[kuangbin带你飞]专题二 搜索进阶 之 A-Eight 反向bfs打表和康拓展开

    [kuangbin带你飞]专题二 搜索进阶 之 A-Eight 这是一道经典的八数码问题.首先,简单介绍一下八数码问题: 八数码问题也称为九宫问题.在3×3的棋盘,摆有八个棋子,每个棋子上标有1至8的 ...

  8. [kuangbin带你飞]专题十 匹配问题

        A-L 二分匹配 M-O 二分图多重匹配 P-Q 二分图最大权匹配 R-S 一般图匹配带花树 模板请自己找     ID Origin Title   61 / 72 Problem A HD ...

  9. [kuangbin带你飞]专题十 匹配问题 一般图匹配

    过去做的都是二分图匹配 即 同一个集合里的点 互相不联通 但是如果延伸到一般图上去 求一个一般图的最大匹配 就要用带花树来解决 带花树模板 用来处理一个无向图上的最大匹配 看了一会还是不懂  抄了一遍 ...

随机推荐

  1. SQL Server取系统当前时间【转】

    getdate //获得系统当前日期 datepart //获取日期指定部分(年月日时分表) getdate()函数:取得系统当前的日期和时间.返回值为datetime类型的. 用法:getdate( ...

  2. caller 属性和callee属性

    1.caller 属性 返回一个对函数的引用,即调用了当前函数的函数体. functionName.caller :functionName 对象是所执行函数的名称. 说明: 对于函数来说,calle ...

  3. java中的分支

    条件语句:     if-else选择结构:         if(条件块){             代码块1         }else{             代码块2         }   ...

  4. junit类找不到的问题解决

    1. Class not found  *******java.lang.ClassNotFoundException: ******* at java.net.URLClassLoader$1.ru ...

  5. oracle11g导入dmp文件(根据用户)

    已知:用户名.密码.dmp文件 .(指即将导入dmp文件的用户名和密码) 需求:将该dmp文件导入本地oracle11g中. 步骤: 1.将该dmp文件拷贝到G:\oracle11g\admin\or ...

  6. Angular学习(2)- ng-app

    此例子看alert弹出时的效果.当然,最重要的是ng-app="MyApp",这一个是怎么加载的. <!DOCTYPE html> <html ng-app=&q ...

  7. 【jmeter】浅说 think time

    接口每天被5000个人调用,同时在线500人,每天要被调用50000次.  过了没多久测试完成写了一份报告发给项目经理: 并发 | 响应时间 | 应用服务器cpu |数据库服务器cpu |TPS | ...

  8. 【linux】cut

    vim test id name age score 101 paul 18 100 102 suan 11 99 103 peter 18 98 [root@andon ~]# cut -f 2 t ...

  9. php 添加redis扩展(一)

    phpredis 下载地址 http://pan.baidu.com/s/1dFFK0gh  提取码:见评论,(~..)亲身试验,可行 1.下载到目录 cd /usr/local/src 2.解压 t ...

  10. UIview定义背景图片

    UIImage *image = [UIImage imageNamed:@"bgimagename"];    UIView *view = [[UIView alloc]ini ...