hdu 1827 Summer Holiday
http://acm.hdu.edu.cn/showproblem.php?pid=1827
题目意思:在一棵有向图中,每个点都有一个权值,求有多少个连通分量;在某个连通分量中,求出可以遍历这个连通分量的点,使得这个点的权值最小。
思路:如果没有环,则起点就是所要求的点。如果有环且环上的点都能遍历这个边通分量,则选择环上权值最小的点。
定义:每次选择的初始点为根。
选择没有遍历过的点作为根,进行tarjan遍历,如果一开始就进入了环,则要把整个环都标记到根上,取环上的最小值作为根的权值。如果发现可以到达其它根,则把该根去掉。

#include<string.h> #include<stdio.h> #include<iostream> #include<stack> #include<stdlib.h> #include<algorithm> #include<utility> using namespace std; const int maxn = 1005; struct nd { int to,next; }edge[maxn*4]; int ecnt,idx,cnt,tmp,ans; int head[maxn],vis[maxn]; int dfn[maxn],low[maxn]; int as[maxn],bs[maxn],cs[maxn]; stack<int>st; inline void add(int s,int t) { edge[ecnt].to = t; edge[ecnt].next = head[s]; head[s] = ecnt++; } void tarjan(int u,int fa) { int i,v; dfn[u] = low[u] = ++idx; vis[u] = 1; st.push(u); for(i = head[u]; i != -1; i = edge[i].next){ v = edge[i].to; if(dfn[v]==0){ tarjan(v,fa); low[u] = min(low[v],low[u]); }else{ if(cs[v]||cs[bs[v]]){ cnt--; cs[v] = 0; cs[bs[v]] = 0; }//去掉根 if(vis[v]) low[u] = min(low[u],dfn[v]); } } if(low[u]==dfn[u]){ do{ v = st.top(); st.pop(); vis[v] = 0; if(u==fa){ bs[v] = u;//把整个环标记到根 tmp = min(tmp,as[v]); } }while(v!=u); } } int main() { int i,j,k,t,n,m,s; while(scanf("%d%d",&n,&m)==2) { memset(head,-1,sizeof(head)); memset(vis,0,sizeof(vis)); memset(bs,0,sizeof(bs)); memset(dfn,0,sizeof(dfn)); ecnt = 0; idx = 0; for(i = 1; i <= n; ++ i) scanf("%d",as+i); for(i = 0; i < m; ++ i) { scanf("%d %d",&s,&t); if(s!=t) add(s,t); } ans = 0; cnt = 0; for(i = 1; i <= n; ++ i) if(!dfn[i]){ tmp = as[i]; tarjan(i,i); if(bs[i]==i) cs[i] = tmp; else cs[i] = as[i]; cnt++; } for(i = 1; i <= n; ++ i){ ans += cs[i]; cs[i] = 0; } printf("%d %d\n",cnt,ans); } return 0; }