Cactus问题总结

参考资料:2008年信息学国家集训队作业

 

有关仙人掌的问题

1.定义:若为有向图,要求:是一个强联通图,任意一个边只属于一个环;若为无向图,要求:是一个连通图,任意一条边,至多属于一个环

uva10510

    给定一个有向图,判断它是否是一个有向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;	
}

洛谷 p4129

题目描述

仙人掌图(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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值