hdu 3639 Hawk-and-Chicken (强连通 + 反图 + 缩点) && hdu1827 Summer Holiday && hdu 1269 迷宫城堡 && hdu3072 Intelligence System
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream>
#include<algorithm>
#include<stack>
#include<vector>
#define MAXN 1010
using namespace std;
vector<int> g[MAXN],g2[MAXN];
stack<int> st;
int n,w[MAXN],dfn[MAXN],low[MAXN],f[MAXN],index;
int in[MAXN],num,sccw[MAXN];
bool vis[MAXN],instack[MAXN];
void tarjan(int u)//求强连通分支
{
int v;
dfn[u] = low[u] = index++;
st.push(u);
instack[u] = true;
vis[u] = true;
for(int i=0; i<g[u].size(); i++)
{
v = g[u][i];
if(!vis[v])
{
tarjan(v);
low[u] = min(low[u], low[v]);
}
else if(instack[v])
low[u] = min(low[u], dfn[v]);
}
if(dfn[u] == low[u])
{
sccw[num]=INT_MAX;
do
{
v = st.top();
instack[v] = false;
st.pop();
f[v]=num;//记录每一个点所在的强连通分支
sccw[num]=min(w[v],sccw[num]);//选择花费最少的人最为该连通分支的代表
}
while(v != u);
num++;
}
}
int main()
{
int m,a,b;
while(scanf("%d %d",&n,&m)==2)
{
for(int i=0;i<=n;i++)
{
g[i].clear();
g2[i].clear();
}
for(int i=1;i<=n;i++)
scanf("%d",&w[i]);
while(m--)
{
scanf("%d %d",&a,&b);
g[a].push_back(b);
}
memset(vis,false,sizeof(vis));
memset(instack,false,sizeof(instack));
num=0;
for(int i=1;i<=n;i++)
{
if(!vis[i])
tarjan(i);
}
memset(in,0,sizeof(in));
for(int i=1;i<=n;i++)
for(int j=0;j<g[i].size();j++)
{
if(f[i]!=f[g[i][j]])
{
g2[f[i]].push_back(f[g[i][j]]);
in[f[g[i][j]]]++;
}
}
int ans=0,sum=0;
for(int i=0;i<num;i++)
if(in[i]==0)
{
ans++;
sum+=sccw[i];
}
printf("%d %d\n",ans,sum);
}
return 0;
}
hdu3639
题意:就是有一个有向图,他们是有传递关系的,问对于图中哪些点是其他最多点能到达他的。
分析:有俩个关键点,第一:就是求一个强连通分支,关于求强连通分支的tarjan 算法,这个博客解释的很详细http://www.cnblogs.com/liyongmou/archive/2010/08/14/1799436.html
因为一个强连通分支里面,任意俩点都是可达的,所有,同一个连通分支里面的点,可到达该点的点数是一样的,这样就可以将一个连通分支缩成一个点
第二:将缩点后的图反向,这样,到达一个点的点数最多的肯定是在入度为0 的强连通分支
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream>
#include<algorithm>
#include<stack>
#include<vector>
#define MAXN 5010
using namespace std;
vector<int> g[MAXN],g2[MAXN];//g保存原图,g2保存缩点反向后的图
stack<int> st;
int n,index,num,dfn[MAXN],low[MAXN],f[MAXN];
bool vis[MAXN],instack[MAXN];
int ans[MAXN],in[MAXN],scc[MAXN],sum;
void tarjan(int u)//求强连通分支
{
int v;
dfn[u] = low[u] = index++;
st.push(u);
instack[u] = true;
vis[u] = true;
for(int i=0; i<g[u].size(); i++)
{
v = g[u][i];
if(!vis[v])
{
tarjan(v);
low[u] = min(low[u], low[v]);
}
else if(instack[v])
low[u] = min(low[u], dfn[v]);
}
if(dfn[u] == low[u])
{
do
{
v = st.top();
instack[v] = false;
st.pop();
f[v]=num;//记录每一个点所在的强连通分支
scc[num]++;//保存该强连通分支的点数
}
while(v != u);
num++;
}
}
void dfs(int u)//深搜求出可到达该连通分支的点数
{
vis[u]=true;
sum+=scc[u];
for(int i=0;i<g2[u].size();i++)
if(!vis[g2[u][i]])
dfs(g2[u][i]);
}
int main()
{
int T,m,a,b,cas=0;
scanf("%d",&T);
while(T--)
{
scanf("%d %d",&n,&m);
for(int i=0;i<=n;i++)//注意要将向量清空
{
g[i].clear();
g2[i].clear();
}
while(m--)
{
scanf("%d %d",&a,&b);
g[a].push_back(b);
}
index=num=0;
memset(vis,false,sizeof(vis));
memset(instack,false,sizeof(instack));
memset(scc,0,sizeof(scc));
for(int i=0;i<n;i++)
{
if(!vis[i])
tarjan(i);
}
memset(in,0,sizeof(in));
for(int i=0;i<n;i++)//重新构图
for(int j=0;j<g[i].size();j++)
{
if(f[i]!=f[g[i][j]])
{
g2[f[g[i][j]]].push_back(f[i]);
in[f[i]]++;
}
}
int maxans=-1;
memset(ans,-1,sizeof(ans));
printf("Case %d: ",++cas);
for(int i=0;i<num;i++)
{
if(in[i]==0)
{
sum=0;
memset(vis,false,sizeof(vis));
dfs(i);
ans[i]=sum;
if(sum>maxans)
maxans=sum;
}
}
printf("%d\n",maxans-1);
int flag=0;
for (int i = 0; i < n; i++)
if ( ans[f[i]] == maxans)
{
if(!flag){
printf("%d", i);
flag=1;
}
else printf(" %d",i);
}
printf("\n");
}
return 0;
}
hdu1827
中文题,题意就不说了
分析:要求出最小的联系人数,首先,我们知道,在一个强连通图里面,任意俩个人都是可达的,所以,对于一个强连通分支,可以选择一个花费最少的人作为代表,缩成一个点。
求出所有强连通分量之后,所有入度为0 的强连通分支都是必须联系的。所以,最小花费和最少人数都满足了
hdu1269
很直接了,判断强连通分量是否等于1
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream>
#include<algorithm>
#include<stack>
#include<vector>
#define MAXN 10010
using namespace std;
vector<int> g[MAXN];
stack<int> st;
int n,dfn[MAXN],low[MAXN],index;
int num;
bool vis[MAXN],instack[MAXN];
void tarjan(int u)//求强连通分支
{
int v;
dfn[u] = low[u] = index++;
st.push(u);
instack[u] = true;
vis[u] = true;
for(int i=0; i<g[u].size(); i++)
{
v = g[u][i];
if(!vis[v])
{
tarjan(v);
low[u] = min(low[u], low[v]);
}
else if(instack[v])
low[u] = min(low[u], dfn[v]);
}
if(dfn[u] == low[u])
{
do
{
v = st.top();
instack[v] = false;
st.pop();
}
while(v != u);
num++;
}
if(num>=2)
return;
}
int main()
{
int m,a,b;
while(scanf("%d %d",&n,&m)==2 && (n||m))
{
for(int i=0;i<=n;i++)
g[i].clear();
while(m--)
{
scanf("%d %d",&a,&b);
g[a].push_back(b);
}
memset(vis,false,sizeof(vis));
memset(instack,false,sizeof(instack));
num=0;
for(int i=1;i<=n;i++)
{
if(!vis[i])
tarjan(i);
if(num>=2)
break;
}
if(num==1)
puts("Yes");
else puts("No");
}
return 0;
}
hdu3072 Intelligence System
题意:强连通缩点后,内部的值无效,求缩点后的有向无环图的,到达所有点的最短距离和
做法:强连通模板(缩点),寻找 每个点的 从其他点到该点的最小权值的边
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream>
#include<stack>
#include<algorithm>
#include<vector>
#define MAXN 50010
using namespace std;
stack<int> st;
int n,num,dfn[MAXN],low[MAXN],f[MAXN],index;
bool vis[MAXN],instack[MAXN];
int sum,ans[MAXN];
struct edge1
{
int v,w;
edge1(int a=0,int c=0):v(a),w(c){}
};
vector<edge1> g[MAXN];
void tarjan(int u)
{
vis[u]=true;
st.push(u);
instack[u]=true;
dfn[u]=low[u]=index++;
int v;
for(int i=0;i<g[u].size();i++)
{
v=g[u][i].v;
if(!vis[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(instack[v])
low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u])
{
do
{
v=st.top();
f[v]=num;
st.pop();
instack[v]=false;
}while(v!=u);
num++;
}
}
int main()
{
int m,a,b,c;
while(scanf("%d %d",&n,&m)==2)
{
for(int i=0;i<n;i++)
g[i].clear();
while(m--)
{
scanf("%d %d %d",&a,&b,&c);
g[a].push_back(edge1(b,c));
}
memset(vis,false,sizeof(vis));
memset(instack,false,sizeof(instack));
num=index=0;
for(int i=0;i<n;i++)
{
if(!vis[i])
tarjan(i);
}
for(int i=0;i<num;i++)
ans[i]=INT_MAX;
for(int i=0;i<n;i++)
{
for(int j=0;j<g[i].size();j++)
{
int v=g[i][j].v;
if(f[i]!=f[v])
{
ans[f[v]]=min(ans[f[v]],g[i][j].w);
}
}
}
sum=0;
for(int i=0;i<num;i++)
if(ans[i]!=INT_MAX)
sum+=ans[i];
printf("%d\n",sum);
}
return 0;
}