基环树也叫环套树(明明更像树套环)
就是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);
}
}