神奇的结构体

在篇头,首先需要感谢的就是给予作者指导的神 ( 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);
}
原文地址:https://www.cnblogs.com/Point-King/p/14593618.html