Tarjan算法——有向圖環問題的killer
P2863 [USACO06JAN]牛的舞會The Cow Prom
題目
問節點大於1的強聯通分量個數
題解
染色的代碼稍微修改即可
#include<bits/stdc++.h>
#define maxl 400010
using namespace std;
int n,k,ff,top,cnt,cnt2,id,num,m,m1,m2;
int a[maxl],b[maxl],s[maxl];
int ehead[maxl],ehead2[maxl];
struct ed
{
int to,w,nxt;
}e[maxl],e2[maxl];
int f[maxl],dfn[maxl],low[maxl];
bool in[maxl];
bool ansflag;
//鏈式前向星
inline void add(int u,int v)
{
e[++cnt].to=v;e[cnt].nxt=ehead[u];ehead[u]=cnt;
}
inline void tarjan(int u)
{
int v;s[++top]=u;in[u]=true;
dfn[u]=low[u]=++num;
for(int i=ehead[u];i;i=e[i].nxt)
{
v=e[i].to;
if(!dfn[v])
{
tarjan(v);
if(low[v]<low[u])
low[u]=low[v];
}
else if(in[v] && dfn[v]<low[u])
low[u]=dfn[v];
}
if(dfn[u]==low[u])
{
ff++;
do
{
v=s[top];s[top]=0;top--;
f[ff]++;
in[v]=false;
}while(v!=u);
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++){
scanf("%d%d",&m1,&m2);
add(m1,m2);
}
for(int i=1;i<=n;i++){
if(!dfn[i]) tarjan(i);
}
int ans=0;
for(int i=1;i<=ff;i++){
if(f[i]>1) ans++;
}
cout<<ans<<endl;
}
P2341 [HAOI2006]受歡迎的牛|【模板】強連通分量
題目
求有向圖中所有點都能到達的點
題解
我們考慮加入圖中沒有環的情況,那麼出度爲0的點就一定就是這個點,加入有兩個及以上出度爲0的點,則這樣的點不存在,因爲這幾個出度爲0的點必不能互相到達
然後再考慮有環的情況,有環的情況比較複雜,但考慮到環內所有點的地位相同,作用相同,所以我們將每一個環都縮成一個點,這樣就能按照上述情況討論了(雖說是模板題,其實還是挺有難度的)
所謂的縮點實質在我看來就是先把一個強聯通分量中的所有點染上同樣的顏色,然後遍歷每一條邊的時候只要邊的起點與終點點的顏色相同,就跳過這條邊,如果不同,就對兩個點的聯通分量的編號進行操作(而不是直接對兩個點進行操作),這樣就達成了縮點的目的
#include<bits/stdc++.h>
#define maxl 400010
using namespace std;
int n,k,ff,top,cnt,cnt2,id,num,m,m1,m2;
int a[maxl],b[maxl],s[maxl],du[maxl];
int head[maxl],head2[maxl];
struct ed
{
int to,w,nxt;
}e[maxl],e2[maxl];
int f[maxl],dfn[maxl],low[maxl];
bool in[maxl];
bool ansflag;
//鏈式前向星
inline void add(int u,int v)
{
e[++cnt].to=v;
e[cnt].nxt=head[u];
head[u]=cnt;
}
inline void tarjan(int u)
{
int v;s[++top]=u;in[u]=true;
dfn[u]=low[u]=++num;
for(int i=head[u];i;i=e[i].nxt)
{
v=e[i].to;
if(!dfn[v])
{
tarjan(v);
if(low[v]<low[u])
low[u]=low[v];
}
else if(in[v] && dfn[v]<low[u])
low[u]=dfn[v];
}
if(dfn[u]==low[u])
{
ff++;
do
{
v=s[top];s[top]=0;top--;
f[v]=ff;
in[v]=false;
}while(v!=u);
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++){
scanf("%d%d",&m1,&m2);
add(m1,m2);
}
for(int i=1;i<=n;i++){
if(!dfn[i]) tarjan(i);
}
for(int i=1;i<=n;i++){
for(int j=head[i];j;j=e[j].nxt){
m=e[j].to;
if(f[i]!=f[m]){
du[f[i]]++;
}
}
}
int ans=0,t=0;
for(int i=1;i<=ff;i++){
if(du[i]==0){
ans++;
if(ans>1) continue;
for(int j=1;j<=n;j++){
if(f[j]==i) t++;
}
}
}
if(ans!=1) cout<<'0'<<endl;
else cout<<t<<endl;
}
P1262 間諜網絡
題目
給出幾個節點的權值,詢問是否可以通過這幾個點遍歷到所有的點,如果可以問所需點權值的最小值,如果不可以輸出不能遍歷到的點的序號最小值
題解
和上一題差不多吧,縮點後無環的有向圖,入度爲0的點是不需要選取的點,只要入度爲0的點取不了答案就是NO,都取得了的話,就把所有點的權值相加就行(注意,縮點後點的權值爲環中權值最小點的權值)
#include<bits/stdc++.h>
#define maxl 400010
using namespace std;
const int M=40000;
int n,k,ff,top,cnt,cnt2,id,num,m,m1,m2;
int a[maxl],b[maxl],s[maxl],v1[maxl],f1[maxl],du[maxl];
int head[maxl],head2[maxl];
struct ed
{
int to,w,nxt;
}e[maxl],e2[maxl];
int f[maxl],dfn[maxl],low[maxl];
bool in[maxl];
bool ansflag;
//鏈式前向星
inline void add(int u,int v)
{
e[++cnt].to=v;
e[cnt].nxt=head[u];
head[u]=cnt;
}
inline void tarjan(int u)
{
int v;s[++top]=u;in[u]=true;
dfn[u]=low[u]=++num;
for(int i=head[u];i;i=e[i].nxt)
{
v=e[i].to;
if(!dfn[v])
{
tarjan(v);
if(low[v]<low[u])
low[u]=low[v];
}
else if(in[v] && dfn[v]<low[u])
low[u]=dfn[v];
}
if(dfn[u]==low[u])
{
ff++;
f1[ff]=M;
do
{
v=s[top];s[top]=0;top--;
f[v]=ff;
if(v1[v]) f1[ff]=min(f1[ff],v1[v]);
in[v]=false;
}while(v!=u);
}
}
int main()
{
//freopen("1.txt","r",stdin);
cin>>n>>k;
for(int i=1;i<=k;i++){
scanf("%d%d",&m1,&m2);
v1[m1]=m2;
}
cin>>m;
for(int i=1;i<=m;i++){
scanf("%d%d",&m1,&m2);
add(m1,m2);
}
for(int i=1;i<=n;i++){
if(!dfn[i]) tarjan(i);
}
for(int i=1;i<=n;i++){
for(int j=head[i];j;j=e[j].nxt){
m=e[j].to;
if(f[i]!=f[m]) du[f[m]]++;
}
}
int ans=0;
for(int i=1;i<=n;i++){
if(du[f[i]]==0){
if(f1[f[i]]==M){
cout<<"NO\n"<<i<<endl;
return 0;
}
}
}
for(int i=1;i<=ff;i++){
if(du[i]==0) ans+=f1[i];
}
cout<<"YES\n"<<ans<<endl;
//fclose(stdin);
}