在篇头,首先需要感谢的就是给予作者指导的神 ( ext{JacderZhang}) ,感谢。
引子
有一天,作者在虚树板子题的时候突然犯懒了,想利用一个结构同时维护两种树的信息,其中一种树我需要有倍增,而另外一种树我不需要,于是我突发奇想可不可以搞一个结构体,然后我可以选择是否开出我倍增需要的空间,从而起到优化空间和代码复杂度的问题(实际上并没有优化代码复杂度)。
然后我写出了这样的一段代码:
struct Tree{
struct Node{int dep;}tr[N];
Tree(){memset(tr,0,sizeof(tr));}
struct Edge{int nxt,to,val;}e[N<<1];int fir[N];
void add(int u,int v,int w,int i){e[i]=(Edge){fir[u],v,w},fir[u]=i;}
struct Beizeng{
int fa[N][20],data[N][20];
void init(){
for(int i=1;i<20;++i){
for(int j=1;j<=n;++j){
fa[j][i]=fa[fa[j][i-1]][i-1];
data[j][i]=min(data[j][i-1],data[fa[j][i-1]][i-1]);
}
}
}
void lca(int u,int v){
if(tr[u].dep<tr[v].dep) swap(u,v);
/*....*/
}
};
}t,now;
写到这里的时候,作者的 ( ext{vscode}) 就给我疯狂报错,说什么非静态成员引用必须与特定对象相对,错误是在函数 ( ext{lca}) 中的 ( ext{tr}) ,然后我就很迷惑,就上网 ( ext{google}) 了一下,意识到了问题的关键。
他报错的原因是因为我在结构体里面的函数调用的 tr
是一个不确定的变量,里面的结构体 Beizeng
无法得知他外面的所套的 Tree
到底是哪一个,所以针对这个问题,我们就可以有了一个解决方案。
我们可以在 Beizeng
里面开一个指针,然后在定义的时候就将这个指针 Tr
指向外面的 Tree
就可以直接调用了。
然后这个问题就解决了,但是我想要的空间复杂度上的优化并没有实现。
进阶
然后发现,我们实际上可以开两个结构体,相应的要不要写倍增,给倍增开空间就可以解决了,但是这样的话外面就需要写两棵树,没有达到代码复杂度降低的目的。
然后我们就可以引入一个叫做继承的概念。就是一个子类可以继承父类的特性,同时可以满足拥有自己的特性,写下来大致是这样。
struct TreeBase {
struct Node{int dep;}tr[N];
struct Edge{int nxt,to,val;}e[N<<1];int fir[N];
void add(int u,int v,int w,int i){e[i]=(Edge){fir[u],v,w},fir[u]=i;}
};
struct TreeWithBeizeng:virtual public TreeBase {
int fa[N][20],data[N][20];
void init(){
for(int i=1;i<20;++i){
for(int j=1;j<=n;++j){
fa[j][i]=fa[fa[j][i-1]][i-1];
data[j][i]=min(data[j][i-1],data[fa[j][i-1]][i-1]);
}
}
}
void lca(int u,int v){
if(tr[u].dep < tr[v].dep) swap(u,v);
}
};
其中继承有三个参数:
- 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
- 保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
- 私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
然后其中的 virtual
实际上是一种判重机制,就是如果一个类继承了多次同一个类,写了 virtual
的类会被只记录一次。
具体的可以去这里学习一下。
然后我们这里就是利用这个继承的特性,然后对应需要的树就开对应的结构体,然后同时节约了空间且简短了代码。
我突然觉得这个博客有点蠢看起来。
拓展
同时的是,作者还在其中学习了关于构造函数的一些问题。
比如我们的初始化 a=1
实际上他是这样的 a(1)
。
这就是为什么 const int a=1;
是可行的,但是 const int a;a=1;
是不可行的了。
因为实际上 const
类型是只接受构造函数的修改的。
然后对于构造函数,他也有两种写法
struct PK_is_a_Fool{
const int IQ;
PK_is_a_Fool(int a):IQ(a){}
PK_is_a_Fool(int a){IQ=a;}
};
但是两者是有区别的,区别和 const
对于两者的区别一样但是注意,第一种不能这么写
struct PK_is_a_Fool{
const int IQ;
PK_is_a_Fool(int a){IQ(a);}
};
这个是错的。
果然这个博客很傻逼,写得都是别人知道的东西。
神的代码
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5;
int n;
struct TreeBase {
struct Node{int dep;}tr[N];
struct Edge{int nxt,to,val;}e[N<<1];int fir[N];
void add(int u,int v,int w,int i){e[i]=(Edge){fir[u],v,w},fir[u]=i;}
};
struct TreeWithBeizeng : virtual public TreeBase {
int fa[N][20],data[N][20];
void init(){
for(int i=1;i<20;++i){
for(int j=1;j<=n;++j){
fa[j][i]=fa[fa[j][i-1]][i-1];
data[j][i]=min(data[j][i-1],data[fa[j][i-1]][i-1]);
}
}
}
void lca(int u,int v){
if(tr[u].dep < tr[v].dep) swap(u,v);
}
};
struct TreeWithSP : virtual public TreeBase {
/* ... */
/*
struct Foo : public bar;
int a : 2;
Foo(int a) : a(a);
int a = flag ? 1 : 0;
for (int v: e[u]) { }
for (auto v_iterator = e[u].begin(), v; _iterator != e[u].end(); ++v_iterator) {
v = *v_iterator;
}
*/
int weight_son[N];
void modify(int u, int v) {
printf("modify(%d, %d)
", u, v);
}
};
struct Tree : public TreeBase, public TreeWithBeizeng, public TreeWithSP {}
t,now;
int main(){
t.modify(1,2);
t.init();
t.lca(1,2);
}