珂学的 vector 存反边,删边

存边,是 \(99\%\) 的图论题都会用到的,OIer 的主流是采取链式前向星的写法,记录 head 数组表示第 \(i\) 个点的第一条边,next 数组表示下一条边的编号,go 数组表示这条边走向哪里,还可以记录个 val 表示这条边的权值。链式前向星的优点是寻找反边,删除边比较方便,缺点是比较难(?,至少我学了 \(3\) 个月)理解,内存访问不连续在大数据下慢。
代码:

int l,hed[N],nxt[N],go[N],val[N];
void add(int u,int v,int w){
	nxt[++l]=hed[u];
	go[l]=v;
	val[l]=w;
	hed[u]=l;
}
void solve(int u){
	for(int j=hed[u];j;j=nxt[j]){
		int v=go[j],w=val[j];
		// solve
	}
}

这个时候就要讲拯救了我 \(3\) 个月的 vector 法。
这里的 vector 并不只是 vector,同样包括 basic_string(unordered_)map/set 等 STL。这里也给出示范。

using pii=std::pair<int,int>;
std::vector<pii>g[N];
void add(int u,int v,int w){
	g[u].emplace_back(v,w);
}
void solve(int u){
	for(auto[v,w]:g[u]){
		// solve
	}
}

对于去除重边,vector 异常方便,只需要对 vector 进行去重就行了!简单的删边就可以使用 (unordered_)map/set 去直接删除。不过会增加一个小小的常数复杂度。

#define all(x) std::begin(x),std::end(x)
sort(all(g[i]));
g[i].erase(unique(all(g[i])),g[i].end());

这个有人肯定又要说了,我想写网络流,要记反边,删前缀边,vector 就做不到了?吗?其实是可以的。
vector 能通过异或 \(1\) 来获取反边的编号,只要在 vector 那边也采用编号的储存开个内存池,vector 里面记录编号就行了。

#define siz(x) int((x).size())
using bsi=std::basic_string<int>;
std::vector<pii>e
bsi g[N];
void add(int u,int v,int w){
	g[u]+=siz(e);e.emplace_back(v,w);
	g[v]+=siz(e);e.emplace_back(u,w);
}
void del(int u){
	for(int j:e){
		auto[v,w]=e[j];
		del[j]=del[j^1]=true;
	}
}

至于删边也是差不多的。

bsi::iterator hed[N];
void init(){
	for(int i=1;i<=n;i++)
		hed[i]=g[i].begin();
}
void del(int u){
	for(auto it=hed[u];it!=g[u].end();++it){
		hed[u]=next(it);// 这个 next 是 STL 里的函数
	}
}

再次介绍一下 basic_stringbasic_string 相当于弱化版的 vector 里面只能存不带有构造函数的类型(这也是 basic_string 没有 emplace_back ),basic_string 的访问插入删除比 vector 更快(小字符串优化),但是空间消耗 basic_string\(32\) 字节而 vector 只要 24 字节(在 \(64\) 位机子上)。其他区别就是 basic_string 的函数更多比如 substr 之类的,更别说 operator+= 以及 basic_string_view 了。

posted @ 2022-11-14 20:07  蒟酱  阅读(318)  评论(0编辑  收藏  举报