SHOI2006仙人掌
Posted hansue
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SHOI2006仙人掌相关的知识,希望对你有一定的参考价值。
分析:
无向仙人掌比较简单。
一个仙人掌首先是个连通图。一遍深搜可以筛掉。
根据定义,如果有任意边被不同的简单环经过,它不是仙人掌。
那么我们现在要对边,计数经过它的环。
这里的做法是树上差分。第一遍深搜还要搞出$DFS$树和倍增数组,遇到非树边记录下来。
$Trickquad vis$数组设三个状态:出边到达$0$点,这条边作为树边,到达的点改成$1$点;到达$1$点,记录成环,自己变成$2$点;到达$2$点,这条成环边已经被记录过,不用管。
用每个点表示它的“到父边”上的计数,就是在每条成环边的端点上$++$,它们的$LCA$上$-=2$,最后再来一遍深搜或者借助$DFN$序由下往上传标记即可。
这样得到的边上的计数不代表实际有多少个简单环经过它。但一旦出现共边的简单环,一定有边上的计数超过$1$。这就够了。
接下来计算“仙人数”。
既然不能破坏连通性,那么不能剪那些不在简单环上的树边。发现去掉任意简单环上的一条边满足条件。于是答案就很显然了。设有$k$个简单环,第$i$个简单环有$s_i$条边,那么$$ans=prodlimits_{i=1}^{k}(s_i+1)$$
用$LCA$算$s$值,加上高精度,没了。
实现(100分):
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<vector> #include<queue> #include<map> #include<set> #define IL inline #define RG register #define _1 first #define _2 second using namespace std; typedef long long LL; const int N=2e4; const int M=1e6; const int L=15; struct Edge{ int to,nxt; Edge(){} Edge(int p1,int p2):to(p1),nxt(p2){} }e[M*2+3]; int etop,h[N+3]; IL void adde(int u,int v){ e[++etop]=Edge(v,h[u]); h[u]=etop; e[++etop]=Edge(u,h[v]); h[v]=etop; } int n,m; int dep[N+3],fa[N+3][L+3]; int cnt,mdep,mlog; int vis[N+3]; int k,v1[M+3],v2[M+3]; void dfs(int u,int fr){ for(int i=h[u];~i;i=e[i].nxt){ int v=e[i].to; if(v==fr) continue; if(vis[v]==1){ k++; v1[k]=u; v2[k]=v; vis[u]=2; } if(vis[v]>0) continue; vis[v]=1; dep[v]=dep[u]+1; mdep=max(mdep,dep[v]); fa[v][0]=u; cnt++; dfs(v,u); } } IL void swap(int &x,int &y){ int t=x; x=y; y=t; } IL int lca(int x,int y){ if(dep[x]<dep[y]) swap(x,y); for(int l=mlog;l>=0;l--) if(dep[fa[x][l]]>=dep[y]) x=fa[x][l]; if(x==y) return x; for(int l=mlog;l>=0;l--) if(fa[x][l]!=fa[y][l]){ x=fa[x][l]; y=fa[y][l]; } return fa[x][0]; } int mrk[N+3]; void dfs2(int u){ for(int i=h[u];~i;i=e[i].nxt){ int v=e[i].to; if(vis[v]) continue; vis[v]=true; dfs2(v); mrk[u]+=mrk[v]; } } const int Len=3180; const LL wgt=1e8; struct Num{ int l; LL a[Len+3]; Num(){ l=1; memset(a,0,sizeof a); } Num(LL x){ l=0; memset(a,0,sizeof a); while(x>0){ a[++l]=x%wgt; x/=wgt; } } IL LL operator[](int i){ return a[i]; } }; IL Num operator+(Num x,Num y){ Num z; z.l=max(x.l,y.l); for(int i=1;i<=z.l;i++){ z.a[i]+=x[i]+y[i]; z.a[i+1]+=z[i]/wgt; z.a[i]%=wgt; } if(z[z.l+1]>0) z.l++; return z; } IL Num operator*(Num x,Num y){ Num z; z.l=x.l+y.l+2; for(int i=1;i<=x.l;i++) for(int j=1;j<=y.l;j++) z.a[i+j-1]+=x[i]*y[j]; for(int i=1;i<=x.l;i++) if(z[i]>=wgt){ z.a[i+1]+=z[i]/wgt; z.a[i]%=wgt; } while(z[z.l]==0) z.l--; return z; } IL Num operator*=(Num &x,Num y){ return x=x*y; } IL void write(Num x){ printf("%lld",x[x.l]); for(int i=x.l-1;i>=1;i--) printf("%08lld",x[i]); } int main(){ scanf("%d%d",&n,&m); etop=-1; memset(h,-1,sizeof h); for(int i=0;i<m;i++){ int k,p,v; scanf("%d%d",&k,&p); for(int j=1;j<k;j++){ scanf("%d",&v); adde(p,v); p=v; } } dep[1]=1; fa[1][0]=fa[0][0]=dep[0]=0; mdep=0; memset(vis,0,sizeof vis); vis[1]=true; cnt=1; dfs(1,0); if(cnt<n){ printf("0"); return 0; } for(mlog=0;mdep>0;mdep>>=1) mlog++; for(int l=1;l<=mlog;l++) for(int i=1;i<=n;i++) fa[i][l]=fa[fa[i][l-1]][l-1]; memset(mrk,0,sizeof mrk); for(int i=1;i<=k;i++){ int x=v1[i],y=v2[i]; int z=lca(x,y); if(x==z){ mrk[y]++; mrk[x]--; } else if(y==z){ mrk[x]++; mrk[y]--; } else { mrk[x]++; mrk[y]++; mrk[z]-=2; } } memset(vis,0,sizeof vis); vis[1]=true; dfs2(1); for(int i=1;i<=n;i++) if(mrk[i]>1){ printf("0"); return 0; } Num ans(1); for(int i=1;i<=k;i++){ int x=v1[i],y=v2[i]; int z=lca(x,y); if(x==z) ans*=Num(1LL*dep[y]-dep[z]+2); else if(y==z) ans*=Num(1LL*dep[x]-dep[z]+2); else ans*=Num(1LL*dep[x]+dep[y]-2*dep[z]+2); } write(ans); return 0; }
小结:
记得讨论范围,没开$long;long$和没上高精然后$WA$掉一样难受。
树上边计数:树上差分。
以上是关于SHOI2006仙人掌的主要内容,如果未能解决你的问题,请参考以下文章
bzoj1023 [SHOI2008]cactus仙人掌图 & poj3567 Cactus Reloaded——求仙人掌直径