强连通分量一共有三种算法Kosaraju、Tarjan、Gabow算法,本人目前只会Tarjan,所以就简单发一下以前所做过的强连通分量的题(当然都是比较简单的~ ~)。
首先是一道裸题。
题意就是求一个 图中强连通分量的个数,只要先把图建好,用到了伪链表,然后用一次Tarjan,就可以顺利求出个数,也不需要记录点属于哪一个强连通分量。
代码:
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn = 200 + 10;
struct edge
{
int y;
int n;
}edges[5000];
int dfn[maxn],low[maxn];
int link[maxn];
int stop,step;
int stack[maxn];
bool instack[maxn];
int n,m;
int ans;
void init()
{
freopen("tyvj1111.in","r",stdin);
freopen("tyvj1111.out","w",stdout);
}
void make(int x,int y)
{
++m;
edges[m].y = y;
edges[m].n = link[x];
link[x] = m;
}
void readdata()
{
scanf("%d",&n);
for(int i = 1;i <= n;i++)
{
int t;
scanf("%d",&t);
while(t)
{
make(i,t);
scanf("%d",&t);
}
}
}
void tarjan(int i)
{
dfn[i]= low[i] = ++step;
instack[i] = true;
stack[++stop] = i;
int t = link[i];
while(t != 0)
{
int y = edges[t].y;
if(!dfn[y])
{
tarjan(y);
if(low[y] < low[i])
low[i] = low[y];
}
else if(instack[y] && dfn[y]<low[i])
{
low[i] = dfn[y];
}
t = edges[t].n;
}
int j;
if(dfn[i] == low[i])
{
++ans;
do
{
j = stack[stop--];
instack[j] = false;
}while(j!=i);
}
}
void solve()
{
ans = 0;
stop = step = m = 0;
memset(dfn,sizeof(dfn),0);
for(int i = 1;i <= n;i++)
{
if(!dfn[i])
tarjan(i);
}
printf("%d",ans);
}
int main()
{
init();
readdata();
solve();
return 0;
}
接下来:相连的农场
在前一道题中多了一个记录强连通分量中点的编号,记录好了按顺序来枚举输出就行了。
代码:
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn = 500 + 10;
int low[maxn],dfn[maxn];
int n;
int ans;
int step,stop;
int stack[maxn];
bool instack[maxn],mark[maxn];
int belong[maxn];
struct pnode
{
int d;
pnode *next;
};
pnode *link[maxn];
void init()
{
freopen("rqnoj480.in","r",stdin);
freopen("rqnoj480.out","w",stdout);
}
void insert(int x,int y)
{
pnode *p = new pnode;
p->d = y;
p->next = link[x];
link[x] = p;
}
void readdata()
{
scanf("%d",&n);
for(int i = 1;i <= n;i++)
for(int j = 1;j <= n;j++)
{
int t;
scanf("%d",&t);
if(t)
{
insert(i,j);
}
}
}
void tarjan(int i)
{
dfn[i] = low[i] = ++step;
pnode *p = link[i];
stack[++stop] = i;
instack[i] = true;
while(p != NULL)
{
int y = p -> d;
if(!dfn[y])
{
tarjan(y);
if(low[y] < low[i])
low[i] = low[y];
}
else if(instack[y] && dfn[y] < low[i])
{
low[i] = dfn[y];
}
p = p -> next;
}
int j;
if(dfn[i] == low[i])
{
++ans;
int cou = 0;
do
{
j = stack[stop--];
instack[j] = false;
belong[j] = ans;
}while(i != j);
}
}
void writeans()
{
printf("%d\n",ans);
for (int i = 1; i < n + 1; ++i)
{
if (mark[belong[i]]) continue;
mark[belong[i]] = true;
printf("%d ",i);
for (int j = i + 1; j < n + 1; ++j)
if (belong[j] == belong[i])
printf("%d ",j);
printf("\n");
}
}
void solve()
{
memset(belong,sizeof(belong),67);
step = ans = stop = 0;
for(int i = 1;i <= n;i++)
{
if(!dfn[i])
{
tarjan(i);
}
}
writeans();
}
int main()
{
init();
readdata();
solve();
return 0;
}
然后就是一道难度更大的题了。
间谍网络
这道题算是我写过的比较长的程序了。首先建图,维护出哪个间谍能够被收买以及收买价格。然后做一次Tarjan,找出每一个强连通分量中能够收买的间谍。接着缩点,建一个新图,找新图中入度为0的点,然后枚举,将新图中入度为0的点全部收买,就能够控制所有间谍了。如果不能控制所有间谍,那就找到第一个不能控制的间谍并输出就行了。
代码:
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn = 3000 + 10;
const int inf = 0x7fffffff;
int n,p;
bool buy[maxn];
int price[maxn];
bool map[maxn][maxn];
int belong[maxn],dfn[maxn],low[maxn],stack[maxn];
bool instack[maxn];
int cou,stop,step;
int id[maxn];
bool flag1;
void init()
{
freopen("tyvj1153.in","r",stdin);
freopen("tyvj1153.out","w",stdout);
}
void readdata()
{
memset(buy,sizeof(buy),false);
memset(map,sizeof(map),false);
scanf("%d",&n);
scanf("%d",&p);
for(int i = 1;i <= p;i++)
{
int t,pri;
scanf("%d%d",&t,&pri);
buy[t] = true;
price[t] = pri;
}
int r;
scanf("%d",&r);
for(int i = 1;i <= r;i++)
{
int x,y;
scanf("%d%d",&x,&y);
map[x][y] = true;
}
}
int tarjan(int x)
{
low[x] = dfn[x] = ++step;
instack[x] = true;
stack[++stop] = x;
for(int i = 1;i <= n;i++)
{
if(map[x][i])
{
if(!dfn[i])
{
tarjan(i);
if(low[i] < low[x])
low[x] = low[i];
}
else if(instack[i] && dfn[i] < low[x])
low[x] = dfn[i];
}
}
if(dfn[x] == low[x])
{
int j;
++cou;
do
{
j = stack[stop--];
instack[j] = false;
belong[j] = cou;
}while(x != j);
}
}
void newmap()
{
for(int i = 1;i <= n;i++)
for(int j = 1;j <= n;j++)
{
if(map[i][j] && belong[i] != belong[j])
{
++id[belong[j]];
}
}
}
int cal()
{
int ans1 = 0;
int ans2 = 0;
bool flag = false;
for(int i = 1;i <= cou;i++)
{
flag = false;
int min = inf;
if(id[i] == 0)
{
for(int j = 1;j <= n;j++)
{
if(belong[j] == i && buy[j])
{
flag = true;
if(price[j] < min)
min = price[j];
}
}
ans1 += min;
if(!flag)break;
}
}
if(!flag)
{
for(int i = 1;i <= n;i++)
{
int fig = belong[i];
int flag2 = false;
for(int j = 1;j <= n;j++)
{
if(belong[j] == fig && buy[j])
{
flag2 = true;
break;
}
}
if(!flag2)
{
ans2 = i;
break;
}
}
}
if(flag)
{
flag1 = true;
return ans1;
}
else
{
flag1 = false;
return ans2;
}
}
void solve()
{
cou = stop = step = 0;
for(int i = 1;i <= n;i++)
{
if(!dfn[i])
tarjan(i);
}
newmap();
int ans = cal();
if(flag1)
{
printf("YES\n");
printf("%d",ans);
}
else
{
printf("NO\n");
printf("%d",ans);
}
}
int main()
{
init();
readdata();
solve();
return 0;
}
总之如果考强连通分量的话多半是需要在一张图中缩点然后再进一步处理,几乎不可能考到裸的,至于还有什么双强连通什么更高级的目前完全不会。看以后有没有机会接触到。
据说noip原题《最优贸易》也能够用Tarjan+缩点+DP来做,但到现在都不知道怎么写。等以后有时间了再来写吧。