基环树,就是有一个环的树。有向基环树又分内向和外向基环树,当然也有无向的。

  最近遇到的基环树真不少。有些题目赤裸裸的就告诉你,“给出一棵基环树(环套树)”,但是有的题会有一些标志。比如说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 }

greedy

三、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 }

dp

四、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 }

dfs

那么本期分享也就结束了,各种表达或理解问题,欢迎各位提出或指正!

转载于:https://www.cnblogs.com/Alan-Luo/articles/9508282.html

基环树一些有趣的事情相关推荐

  1. 基环树DP(bzoj 1040: [ZJOI2008]骑士)

    树:n个点n-1条边的连通图 基环树:n个点n条边的连通图,也就是一个环套着多棵树 基环树DP:找到环上任意相邻两点,断掉这两点之间的边,就形成了一棵树 之后对这两点分别进行一次树形DP即可 例题: ...

  2. “基环树”的简单应用——MAFIJA和WYF互相追逐的头题解

    至于WYF大概是谁,不必多说,结合时事,人渣必须从严处理,从重判刑:代码量比较大,各位耐心看一下 WYF互相追逐的头 题目的阅读量比较大,而且数据比较清奇,但是我们可以知道的是每一个头都在尽力追逐自己 ...

  3. BZOJ1040 骑士(基环树DP)

    Description Z国的骑士团是一个很有势力的组织,帮会中汇聚了来自各地的精英.他们劫富济贫,惩恶扬善,受到社会各 界的赞扬.最近发生了一件可怕的事情,邪恶的Y国发动了一场针对Z国的侵略战争.战 ...

  4. 今天520,有很多有趣的事情

    今天520,有很多有趣的事情 上次写小作文还是在上次,还是在五四青年节,今天520,一个普通学生短短两周又经历了许多有趣的事情. 到目前为止,虽然是在学校,但是还是线上网课的形式,而且,就在昨天,学校 ...

  5. python能在生活中做什么-python能做哪些生活有趣的事情

    躺着赚钱 一位匿名知乎网友爆料用Python写了自动化交易程序,2年躺着赚了200万!相当于普通程序员10年的工资,此刻的心情...你懂的! 不过,这位大侠的真实身份也被网友找出了,真是人红了想低调都 ...

  6. python可以做什么有趣的东西-python能做哪些生活有趣的事情

    躺着赚钱 一位匿名知乎网友爆料用Python写了自动化交易程序,2年躺着赚了200万!相当于普通程序员10年的工资,此刻的心情...你懂的! 不过,这位大侠的真实身份也被网友找出了,真是人红了想低调都 ...

  7. 使用装饰者模式做有趣的事情

    什么是装饰者模式 装饰者模式是一种为函数或类增添特性的技术,它可以让我们在不修改原来对象的基础上,为其增添新的能力和行为.它本质上也是一个函数(在javascipt中,类也只是函数的语法糖). 我们什 ...

  8. 【基环树DP】[NOI2012]迷失游乐园

    题目描述 Description 放假了,小Z觉得呆在家里特别无聊,于是决定一个人去游乐园玩.进入游乐园后,小Z看了看游乐园的地图,发现可以将游乐园抽象成有n个景点.m条道路的无向连通图,且该图中至多 ...

  9. 开心一刻,你和导师之间有什么有趣的事情?

    开心一刻,你和导师之间有什么有趣的事情? 睡觉前收到导师微信 回答一 对话一: 我:导师你在吗?我是新一,我去找你见一下面 导师:我要去美国交流了,这学期不回去了 对话二:导师:新一你在吗?我是你导师 ...

最新文章

  1. Python 标准库之 commands
  2. 查看mysql主外键信息
  3. AngularJs 常用指令标签
  4. python123期末测试第十周答案_Python语言设计_章节测验,期末考试,慕课答案查询公众号...
  5. 截取url的host_java正则表达式获取url的host示例
  6. python-工具-pycharm的基本操作
  7. Fixcel电子表格——报表模块(二)
  8. 【CentOS】又是一篇Shell
  9. php模拟input 的file上传文件
  10. 学生用计算机怎么乘跟跟号号,3根号键的啊函数计算器价格多少,它与普通计 – 手机爱问...
  11. 计算机开机硬盘扫描,解决每次开机都检测硬盘
  12. 什么是小托福考试?适合什么学生报考?
  13. 计算机系统中设置保护系统还原,win7系统电脑设置系统还原点的操作方法
  14. 自学习策略和Levy飞行的正弦余弦优化算法-附代码
  15. js插件之今日诗词、每日一言
  16. 利用pytorch长短期记忆网络LSTM实现股票预测分析
  17. 01背包中背包装满和不装满
  18. spring-IOC-三更的扩展版
  19. 【有手就行】使用你自己的声音做语音合成
  20. xps in html5,好看的轻薄笔记本,戴尔XPS 13 2in-1评测

热门文章

  1. cocoa pods Installation
  2. resin 4.0数据源的配置
  3. Maven 项目的 org.junit.Test 获取不到(转载)
  4. None.org.apache.spark.api.python.PythonAccumulatorV2
  5. Pessimistic Error Pruning example of C4.5
  6. 决策树-熵计算-ID3算法(转)
  7. Jpeglib使用指南, 各种压缩包的压缩和解压方法, 开源社区分裂史
  8. php一点按钮就下载功能源码,php实现强制文件下载方法的源码参考
  9. 微服务接入oauth2_微服务权限终极解决方案,Spring Cloud Gateway+Oauth2实现统一认证和鉴权!...
  10. linux kill命令详解