基环树

124 篇文章 0 订阅
39 篇文章 0 订阅

基环树也叫环套树(明明更像树套环)
就是n个点n条边的连通图,可以发现只有一个环,并且删掉环上任意一个边可以变成一棵树。
尽管许多题解上都会说什么把环求出来然后分类讨论什么的,
但是,实际上删掉环上的一条边然后再加回去分类讨论要好做的多。。。。。
毕竟你不需要树上一个数据结构,再在环上再开一个数据结构。。。。。。。
套路了。

基环树图中找基环的方法:
最稳的,直接写 t a r j a n tarjan tarjan
或者开个 v i s vis vis模拟 t a r j a n tarjan tarjan也行,
注意可能有 ( 1 − > 2 ) , ( 2 − > 1 ) (1->2),(2->1) (1>2),(2>1)这种基环的时候,
d f s dfs dfs存前一条边的编号。
大概就是这个样子:

int ar[maxn],onc[maxn],a[maxn];
int vis[maxn],sta[maxn],tp;
void dfs0(int u,int ff){
	vis[u] = 1;
	sta[++tp] = u;
	for(int i=info[u],v;i;i=Prev[i]) if(i^ff){
		v=to[i];
		if(!vis[v]) dfs0(v,(i^1));
		else if(!onc[v]){
			for(int t=-1;t!=v;){
				t = sta[tp--];
				ar[++ar[0]] = t;
				onc[t] = 1;
			}
			per(i,ar[0],1) sta[++tp] = ar[i];
		}
	}
	if(sta[tp] == u) tp--;
}

例1:HDU 6403 Card Game,简单的分类讨论基环树DP

题意
给定 n 张卡片,每张卡片正反面各有一个数。问至少要翻转多少张卡片,才能使正面向上的数互
不相同,并求方案数。
题解
首先建图:每个数字为一个节点,每张卡片反面数字向正面数字连一条有向边。问题转化为:至
少要反转多少条边的方向,才能使得每个点的入度不会超过 1。我们对每个弱连通分量分别处理。易
知,当底图是树或基环树时,才可能有解。对于基环树,先把环找出来,然后将环上的边的方向统一
一下;非环边的方向则是唯一确定的,从环上的点向外做一遍 dfs 即可。对于树,可以正反两次 dfs
处理出每个点作为根时所需要的反向次数,并统计出最小值以及方案数。最后将答案合并即可。

AC Code :

#include<cstdio>
#include<cstring>
#include<cctype>
#include<algorithm>
#define maxn 200005
#define mod 998244353
using namespace std;

inline void read(int &res){ char ch;for(;!isdigit(ch=getchar()););for(res=ch-'0';isdigit(ch=getchar());res=res*10+ch-'0'); }
int n,info[maxn],Prev[maxn],to[maxn],dir[maxn],cnt_e;
bool vis[maxn];
inline void Node(const int &u,const int &v,const int &typ){Prev[++cnt_e]=info[u],info[u]=cnt_e,to[cnt_e]=v,dir[cnt_e]=typ;}

int cnte=0,cntp=0;
bool ERROR=0;

void check(int now,int ff)
{
	cntp++;
	vis[now] = 1;
	for(int i=info[now];i;i=Prev[i])
	{
		cnte++;
		if(!vis[to[i]]) check(to[i],now);
	}
}

int st=-1,ed=-1,id=-1;
int dp[maxn],dp2[maxn],sta[maxn],tp;

void dfs(int now,int ff)
{
	vis[now] = 1;
	dp[now] = 0;
    for(int i=info[now];i;i=Prev[i])
		if(!vis[to[i]])
		{
			dfs(to[i],now);
			dp[now]+=dp[to[i]]+(!dir[i]);
		}
		else if(to[i]!=ff){ st=now , ed = to[i] , id = i;}
}

int ans = 0 , Min , ways , tway;
void ser(int now,int ff)
{
	sta[tp++] = now;
	Min = min(Min , dp2[now]);
    for(int i=info[now];i;i=Prev[i])
		if(i!=ff && (i^1)!=ff && i!=id && i!=(id^1))
		{
			dp2[to[i]] = dp2[now] + (dir[i] ? 1 : -1);
            ser(to[i],i);
		}
}

int main()
{

	#ifndef ONLINE_JUDGE
	freopen("1.in","r",stdin);
	#endif

	int T,u,v;
	for(read(T);T--;)
	{
		cnt_e=ways=1,ERROR=ans=0;
		memset(info,0,sizeof info);
		memset(vis,0,sizeof vis);
        read(n);
        for(int i=1;i<=n;i++)
		{
			read(u),read(v);
			Node(v,u,1),Node(u,v,0);
		}
        for(int i=1;i<=2*n;i++)
			if(!vis[i])
			{
				cntp=cnte=0;
				check(i,0);
				if(cnte/2 > cntp){ ERROR=1;break; }
			}
		if(ERROR){ puts("-1 -1");continue; }

		memset(vis,0,sizeof vis);
		for(int i=1;i<=2*n;i++)
			if(!vis[i])
			{
				st=ed=id=-1;
				dfs(i,0);

				Min = 0x3f3f3f3f , tway = tp = 0 , dp2[i] = dp[i];
				ser(i,0);
				if(st == -1){for(int j=0;j<tp;j++) if(dp2[sta[j]]==Min) tway++;}
				else Min = min(dp2[st] + dir[id] , dp2[ed] + (!dir[id])),tway=(dp2[st] + dir[id] == dp2[ed] + (!dir[id]) ? 2 : 1);
				ans += Min , ways = 1ll * ways * tway % mod;
			}

		printf("%d %d\n",ans , ways);
	}

}

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值