参考资料:2008年信息学国家集训队作业
有关仙人掌的问题
1.定义:若为有向图,要求:是一个强联通图,任意一个边只属于一个环;若为无向图,要求:是一个连通图,任意一条边,至多属于一个环
给定一个有向图,判断它是否是一个有向Cactus。
可以进行类似tarjan的操作,记录下每个结点的父亲结点,如果遇到一个结点之前遍历过了,那么就把环上的结点都+1(注意当前结点不算,因为多个环可以连在一个结点上),然后如果有一个结点超过了2,就肯定不是仙人掌图了
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=10005;
int dfn[maxn],low[maxn],fa[maxn],cnt[maxn];
int T,n,m,times,col_cnt;
vector <int> G[maxn];
bool find(int u,int v)
{
while(fa[u]!=v)
{
cnt[u]++;
if(cnt[u]>1) return false;
u=fa[u];
}
return true;
}
bool dfs(int u)
{
dfn[u]=low[u]=++times;
for(int k=0;k<G[u].size();k++)
{
int to=G[u][k];
if(!dfn[to])
{
fa[to]=u;
if(!dfs(to)) return false;
low[u]=min(low[u],low[to]);
}
else
{
low[u]=min(low[u],dfn[to]);
if(!find(u,to)) return false;
}
}
if(low[u]==dfn[u])
{
col_cnt++;
if(col_cnt>1) return false;
}
return true;
}
bool check(int x)
{
times=0,col_cnt=0;
memset(cnt,0,sizeof(cnt));
memset(dfn,0,sizeof(dfn));
for(int i=0;i<n;i++)
if(!dfn[i] && !dfs(i))
return false;
return true;
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
G[i].clear();
int u,v;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&u,&v);
G[u].push_back(v);
}
if(check(n)==true)
printf("YES\n");
else printf("NO\n");
}
return 0;
}
题目描述
仙人掌图(cactus)是一种无向连通图,它的每条边最多只能出现在一个简单回路(simple cycle)里面。从直观上说,可以把仙人掌图理解为允许存在回路的树。但是仙人掌图和树之间有个本质的不同,仙人掌图可以拥有多个支撑子图(spanning subgraph),而树的支撑子图只有一个(它自身),我们把仙人掌图的支撑子图的数目称为“仙人数”。你的任务就是计算给定图的“仙人数”。
一些关于仙人掌图的举例:
第一张图是一个仙人掌图,第二张图的边(2,3)在两个不同的回路里面,所以不是仙人掌图,第三张图不是一个连通图,所以也不是仙人掌图。
以下是对一些术语的解释:
简单回路(simple cycle):简单回路是原图的一条路径,这条路径的边集构成了回路,回路中顶点只能出现一次。比如对于上例中第二个图来说,它一共有三个简单回路,分别是(4,3,2,1,6,5)、(7,8,9,10,2,3)和(4,3,7,8,9,10,2,1,6,5)
支撑子图(spanning subgraph):支撑子图也是原图的子图,这种子图可以比原来少一些边,但是不能破坏图的连通性,也不能去除原来图上的任何顶点。“支撑”的概念类似于我们熟知的“最小支撑树”,对于上例中的第一张图来说,任意去除回路I中的图或回路II中的一条边都能构成一个支撑子图,所以它的支撑子图一共有6 + 4 + 6 × 4 + 1 = 35种(注意图自身也是自己的一个子图)
输入格式
输入文件的第一行是两个整数n和m(1≤n≤20000, 0≤m≤1000)。n代表图的顶点数,顶点的编号总是从1到n表示的。
接下来一共有m行。每行都代表了图上的一条路径(注意:这里所表示的一条路径可不一定是一条回路)。这些行的格式是首先有一个整数ki(2≤ki≤1000)代表这条路径通过了几个顶点,接下来是ki个在1到n之间的数字,其中每个数字代表了图上的一个顶点,相邻的顶点之间就定义了一条边。一条路径上可能通过一个顶点好几次,比如对于第一个例子,第一条路径从2经过3,又从8返回到了3,但是我们保证所有的边都会出现在某条路径上,而且不会重复出现在两条路径上,或者在一条路径上出现两次。
输出格式
输出这张图的“仙人数”,如果它不是一张仙人掌图,输出0。注意最后的答案可能是一个很大很大的数。
输入输出样例
输入 #1
14 3 9 1 2 3 4 5 6 7 8 3 7 2 9 10 11 12 13 10 2 2 14
输出 #1
35
输入 #2
10 2 7 1 2 3 4 5 6 1 6 3 7 8 9 10 2
输出 #2
0
输入 #3
5 1 4 1 2 3 4
输出 #3
0
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e4+5,maxm=1e6+5;
int n,m,dfn[maxn],du[maxn],low[maxn];
int head[maxn],fa[maxn],dep[maxn];
int cnt,tot,times;
struct edge
{
int to,nxt;
}e[maxm<<1];
struct gjd
{
int s[6200],len;
gjd(){memset(s,0,sizeof(s)); len=0;}
gjd operator =(int x)
{
while(x) s[++len]=x-x/10*10,x/=10;
return *this;
}
gjd operator *(const gjd&x)
{
gjd ans;
int maxlen=x.len+len-1;
for(int i=1;i<=len;i++)
for(int j=1;j<=x.len;j++)
ans.s[i+j-1]+=s[i]*x.s[j];
for(int i=1;i<=maxlen;i++)
if(ans.s[i]>=10) ans.s[i+1]+=ans.s[i]/10,ans.s[i]=ans.s[i]-ans.s[i]/10*10;
while(ans.s[maxlen+1])
{
++maxlen;
if(ans.s[maxlen]>=10) ans.s[maxlen+1]+=ans.s[maxlen]/10,ans.s[maxlen]=ans.s[maxlen]/10*10;
}
return ans.len=maxlen,ans;
}
void print()
{
for(int i=len;i;i--)
printf("%d",s[i]);
}
}ans;
void add(int x,int y)
{
e[++tot].nxt=head[x];
head[x]=tot;
e[tot].to=y;
}
void calc(int st,int en)
{
for(int i=en;i!=st;i=fa[i])
if(++du[i]==2)
{
printf("0\n");
exit(0);
}
gjd tmp;
tmp=(dep[en]-dep[st]+2);
ans=ans*tmp;
}
void tarjan(int x)
{
cnt++;
dfn[x]=low[x]=++times;
for(int i=head[x];i;i=e[i].nxt)
{
int to=e[i].to;
if(to==fa[x]) continue;
if(!dfn[to])
{
dep[to]=dep[x]+1;
fa[to]=x;
tarjan(to);
low[x]=min(low[x],low[to]);
}
else low[x]=min(low[x],dfn[to]);
}
for(int i=head[x];i;i=e[i].nxt)
{
int to=e[i].to;
if(fa[to]!=x && dfn[x]<dfn[to])
calc(x,to);
}
}
int main()
{
//freopen("a.in","r",stdin);
//freopen("a.out","w",stdout);
scanf("%d%d",&n,&m);
int t,x,y;
ans.s[1]=1; ans.len=1;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&t,&x);
for(int j=2;j<=t;j++)
{
scanf("%d",&y);
add(x,y); add(y,x);
x=y;
}
}
tarjan(1);
if(cnt!=n)
{
printf("0\n");
return 0;
}
ans.print();
return 0;
}