基环树一些有趣的事情
基环树,就是有一个环的树。有向基环树又分内向和外向基环树,当然也有无向的。
最近遇到的基环树真不少。有些题目赤裸裸的就告诉你,“给出一棵基环树(环套树)”,但是有的题会有一些标志。比如说n个点n条边(即n种关系),甚至有的题用更隐晦的手法来描写n个点n条边。那今天我就分享一些题目,尤其是后几道题目,非常有趣!
一、像HDU5915,CF835F,BZOJ1791,BZOJ2878这种明摆着是基环树的题,大家可以做一做,但这里不详细讲做法
(不是因为这些题简单,甚至这些题非常难,但有些无聊。)
二、BZOJ 3037 创世纪;
这是中文题,不解释题面。“每种世界元素都可以限制另外一种世界元素”,这句话暗示了n个点,n条边,于是把a限制b当成a向b连一条有向边。于是很显然,一个点可以被使用的前提是有一个未被使用的前驱,所以可以dp,但是贪心算法也是可以的,贪心策略是一开始将每个入度为零的点加入队列,然后顺次按要求选择;最后把环拎出来,环长/2加到答案里,这道题就结束了;正确性可以自行证明,或看这里;
有代码
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 #include<cmath> 5 #include<algorithm> 6 #include<queue> 7 using namespace std; 8 const int MAXN=1000005; 9 int n,in[MAXN],a[MAXN],ans=0; 10 bool vis[MAXN];queue<int>q; 11 int main(){ 12 scanf("%d",&n); 13 for(int i=1;i<=n;i++) 14 scanf("%d",&a[i]),in[a[i]]++; 15 for(int i=1;i<=n;i++) 16 if(!in[i]) q.push(i); 17 while(q.size()){ 18 if(!vis[a[q.front()]]&&!vis[q.front()]){ 19 vis[a[q.front()]]=1;ans++; 20 if(--in[a[a[q.front()]]]==0) 21 q.push(a[a[q.front()]]); 22 } vis[q.front()]=1;q.pop(); 23 } 24 for(int i=1;i<=n;i++) 25 if(!vis[i]){ 26 int cnt=1,t=i; 27 while(a[t]!=i) cnt++,vis[t]=1,t=a[t]; 28 vis[t]=1;ans+=cnt>>1; 29 } printf("%d\n",ans);return 0; 30 }
三、BZOJ1040 骑士 ;
这道题也是一样,限制了每个人有一个仇敌。就是环套树的关系。仔细观察题目我们会发现,任何两个有边的点都不能被同时选入骑士团中,所以这是一颗无向基环树。我们可以用树形dp来做,从某一条在环上的边断环成链,然后树中就没有环了,可以直接进行树形dp,这样,可以从被我们断掉的那条边连接的两点入手,先强制不选某一个,做一遍树上dp,再强制不选另一个,再做一遍,两遍最优解取max就是最终答案!为什么这样能体现所有的情况呢?各位可以思考一下。
也有代码
1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 #include<algorithm> 5 #define ll long long 6 using namespace std; 7 const int MAXN=1000005; 8 struct node{ 9 int y,nxt; 10 }edge[MAXN*2];int indx=0; 11 int ra[MAXN],rb[MAXN]; 12 int fa[MAXN],head[MAXN],n,m=0; 13 ll f[MAXN],g[MAXN],v[MAXN],ans,t=0; 14 int get(int x){return fa[x]==x?x:fa[x]=get(fa[x]);} 15 ll max(ll a,ll b){return a>b?a:b;} 16 void add(int x,int y){ 17 edge[++indx].nxt=head[x]; 18 edge[indx].y=y;head[x]=indx; 19 } 20 void dfs(int x,int pre){ 21 f[x]=v[x],g[x]=0; 22 for(int i=head[x],y;~i;i=edge[i].nxt){ 23 if((y=edge[i].y)==pre) continue; 24 dfs(y,x);g[x]+=max(f[y],g[y]); 25 f[x]+=g[y]; 26 } 27 } 28 int main(){ 29 scanf("%d",&n); 30 for(int i=1;i<=n;i++) fa[i]=i; 31 memset(head,-1,sizeof(head)); 32 for(int i=1,y;i<=n;i++){ 33 scanf("%lld%d",&v[i],&y); 34 if(get(i)!=get(y)){ 35 add(i,y);add(y,i); 36 fa[fa[i]]=fa[y]; 37 } else ra[++m]=i,rb[m]=y; 38 } 39 for(int i=1;i<=m;i++){ 40 dfs(ra[i],0);t=g[ra[i]]; 41 dfs(rb[i],0);t=max(t,g[rb[i]]); 42 ans+=t; 43 } printf("%lld\n",ans);return 0; 44 }
四、RQN PID730混合药品
又是一道两两不能放在一起的模型,现在我们对于这样的模型应该已经非常熟悉了。对于这道题,我们需要分成环部分和树部分进行考虑,首先明确我们要在一棵基环树上进行k染色,然后有边的两个点颜色不同,对于环来说,我们要dp求出染色方案,对于树来说,满足要求的方案数可以用组合数学来解,公式是(k-1)^siz,siz代表树的大小(不包括在环上的那个点)。最后用乘法原理把方案数相乘作为最终答案!
更有代码
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<algorithm> 5 #include<cstring> 6 #define ms(a,x) memset(a,x,sizeof(a)) 7 using namespace std; 8 const int MAXN=105; 9 const int mod=1e9+7; 10 long long n,m,k,t,tot,ans,p,num;bool vis[MAXN]; 11 long long c[MAXN],f[MAXN],fa[MAXN];bool bz[MAXN]; 12 int main(){ 13 scanf("%lld",&t); 14 while(t--){ 15 scanf("%lld%lld",&n,&k);num=n;ms(vis,0); 16 for(int i=0;i<n;i++) scanf("%lld",&fa[i]); 17 f[1]=k;f[2]=k*(k-1)%mod;f[3]=f[2]*(k-2)%mod;ans=1; 18 for(int i=4;i<=n;i++) f[i]=((f[i-1]*(k-2)%mod)+(f[i-2]*(k-1)%mod))%mod; 19 for(int i=0;i<n;i++) 20 if(!vis[i]){ 21 for(p=i,tot=0,ms(bz,0);!bz[p];bz[p]=1,p=fa[p],tot++); 22 if(p^i) continue; 23 for(p=i,ms(bz,0);!bz[p];bz[p]=vis[p]=1,p=fa[p]); 24 num-=tot;ans=(1LL*ans*f[tot])%mod; 25 } 26 for(int i=1;i<=num;i++){ 27 ans=ans*(k-1)%mod; 28 } printf("%lld\n",ans); 29 } return 0; 30 }
五、前面我们见到了求最优解的题,也有求方案数的题,下面我们看一道既要求出最优解,也要求出方案数的题;
HDU6403:题意:给定 n 张卡片,每张卡片正反面各有一个数。问至少要翻转多少张卡片,才能使正面向上的数互
不相同,并求方案数。
啊,这个题就confusing了,这看似二分图又做不了,看似基环树又无法建图的题,可怎么办啊。。。
不要着急,我们仔细地挖掘一下题目中的信息,每张卡片连接了两个数,这是不是就像一个边连接两个点的关系?于是我们就有思路了,但在处理关系的时候我们要稍微动一下脑筋。我们从当前每张卡的背面的数向正面的数连一条有向边,这样,所有有入度的点就是朝上的数字,我们要求朝上的数字各不相同,是不是就可以抽象成“使任意点的入度小于等于一,最少需要翻转几条边!”,那这个题的模型我们就知道了,是建双向边,用0/1区分方向的题。我们首先把环上的方向确定,然后在树上我们统计每个点作为根时各边的最小翻转次数,然后最终将方案数合并。(处理过于麻烦,本蒻选择直接抄题解。。。)
还有代码
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<cstring> 5 #include<algorithm> 6 using namespace std; 7 const int MAXN=200005; 8 const int mod=998244353; 9 struct node{ 10 int y,z,nxt; 11 }edge[MAXN];int indx=0,s=-1,t=-1; 12 int mn,ways,ans=0,tway; 13 int head[MAXN],n,m,k,cnt=0,num=0,sign=-1; 14 bool vis[MAXN],flag;int f[MAXN],g[MAXN]; 15 char readchar(){ 16 static char buf[100000], *l=buf,*r=buf; 17 if(l==r) r=(l=buf)+fread(buf,1,100000,stdin); 18 if(l==r) return EOF; 19 return *l++; 20 } 21 inline int read(){ 22 int x=0,f=1;char ch=readchar(); 23 while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=readchar();} 24 while(ch<='9'&&ch>='0'){x=x*10+ch-'0';ch=readchar();} 25 return x*f; 26 } 27 void add(int x,int y,int z){ 28 edge[++indx].y=y;edge[indx].z=z; 29 edge[indx].nxt=head[x];head[x]=indx; 30 } int st[MAXN],tp=0; 31 void check(int x){ 32 num++;vis[x]=1; 33 for(int i=head[x],y;i;i=edge[i].nxt){ 34 cnt++;if(!vis[y=edge[i].y]) check(y); 35 } return ; 36 } 37 void dfs(int x,int fa){ 38 vis[x]=1;f[x]=0; 39 for(int i=head[x],y;i;i=edge[i].nxt) 40 if(!vis[y=edge[i].y]){ 41 dfs(y,x);f[x]+=f[y]+(!edge[i].z); 42 } else if(y!=fa) s=x,t=y,sign=i; 43 } 44 void solve(int x,int fa){ 45 st[tp++]=x;mn=min(mn,g[x]); 46 for(int i=head[x],y;i;i=edge[i].nxt) 47 if(i!=fa&&(i^1)!=fa&&i!=sign&&i!=(sign^1)){ 48 g[y=edge[i].y]=g[x]+(edge[i].z?1:-1); 49 solve(y,i); 50 }return ; 51 } 52 int main(){ 53 int ca=read(); 54 while(ca--){ 55 indx=ways=1;flag=0;ans=0; 56 memset(head,0,sizeof(head)); 57 memset(vis,0,sizeof(vis)); 58 n=read(); 59 for(int i=1,x,y;i<=n;i++){ 60 x=read(),y=read(); 61 add(y,x,1);add(x,y,0); 62 } 63 for(int i=1;i<=2*n;i++) 64 if(!vis[i]){ 65 num=cnt=0;check(i); 66 if(cnt/2>num){flag=1;break;} 67 } if(flag){puts("-1 -1");continue;} 68 memset(vis,0,sizeof(vis)); 69 for(int i=1;i<=2*n;i++) 70 if(!vis[i]){ 71 s=t=sign=-1;dfs(i,0); 72 mn=0x3fffffff;tway=tp=0;g[i]=f[i]; 73 solve(i,0);if(s==-1){ 74 for(int j=0;j<tp;j++) 75 if(g[st[j]]==mn) tway++; 76 } else 77 mn=min(g[s]+edge[sign].z,g[t]+(!edge[sign].z)), 78 tway=(g[s]+edge[sign].z==g[t]+(!edge[sign].z)?2:1); 79 ans+=mn;ways=1LL*ways*tway%mod; 80 } printf("%d %d\n",ans,ways); 81 } return 0; 82 }
那么本期分享也就结束了,各种表达或理解问题,欢迎各位提出或指正!
转载于:https://www.cnblogs.com/Alan-Luo/articles/9508282.html