P3401 洛谷树 题解

P3401 洛谷树 题解

隐形霉菌死于此树下。

解法

考虑树链剖分,首先先将父亲与儿子间的边权转化为儿子的点权。

根据异或的性质, a ⊕ a = 0 a \oplus a = 0 aa=0,我们记每个节点到根的边权异或和为 v a l i val_i vali,任意两点间的边权异或和为 d i s i , j dis_{i,j} disi,j,这样,我们可以将树上任意两点 x , y x,y x,y 间边权的异或和表示为 v a l x ⊕ v a l y val_x \oplus val_y valxvaly。考虑如下证明: v a l x ⊕ v a l y = v a l x ⊕ v a l l c a ⊕ v a l l c a ⊕ v a l y = d i s x , l c a ⊕ d i s l c a , y = d i s x , y val_x \oplus val_y = val_x \oplus val_{lca} \oplus val_{lca} \oplus val_y = dis_{x,lca} \oplus dis_{lca,y} = dis_{x,y} valxvaly=valxvallcavallcavaly=disx,lcadislca,y=disx,y,其中 l c a lca lca 表示 x x x y y y 的最近公共祖先。

原问题被转化为 x x x y y y 间路径上的任意两点间的异或和的和。

注意到交换求和顺序是不影响答案的,并且此题值域很小,所以我们将题目从每个点对的每一个二进制位的贡献转化为了每一个二进制位上每个点对的贡献。

考虑异或的性质,只有 0 ⊕ 1 = 1 0 \oplus 1 = 1 01=1,即一个点对的某个二进制位不同时这个点对在这个二进制位上才有贡献,我们可以使用线段树维护每一个二进制位上有 c n t 0 cnt0 cnt0 0 0 0 c n t 1 cnt1 cnt1 1 1 1,根据简单的组合,这个二进制位有 c n t 0 × c n t 1 cnt0 \times cnt1 cnt0×cnt1 次贡献,对和的贡献为 c n t 0 × c n t 1 × 2 i cnt0 \times cnt1 \times 2^i cnt0×cnt1×2i

线段树维护 v a l val val 的值即可。

至于修改操作,我们设节点 x x x 原来的点权(我们已经将边权转化为了点权)为 w x w_x wx,要修改成的值为 y y y,那新的 v a l x val_x valx 即为 v a l x ⊕ w x ⊕ y val_x \oplus w_x \oplus y valxwxy,异或 w x w_x wx 的目的是撤销曾经 w x w_x wx v a l x val_x valx 的贡献。注意到这个修改会影响到节点 x x x 的子树的 v a l val val 值,所以,我们对其子树进行同样的操作即可。

操作一为树链剖分,线段树区间查询,操作二为字数操作,线段树区间修改。

代码

#include<bits/stdc++.h>
namespace fast_IO
{
	/**
	 * 屎山快读快写
	*/
};
using namespace fast_IO;
const int N=30010;
int n,m,dep[N],fa[N],siz[N],hvson[N],id[N],times,start[N],w[N],neww[N],v[N],val[10];
struct edge
{
	int to,cost;
	edge *next;
};
edge *head[N];
inline void add(const int x,const int y,const int z)
{
	edge *e=new edge;
	e->to=y,e->next=head[x],e->cost=z,head[x]=e;
}
inline void dfs1(int pos,int f,int depth,int maxi=0)
{
	fa[pos]=f,dep[pos]=depth,siz[pos]=1;
	for(edge *i=head[pos];i!=nullptr;i=i->next)
		if(i->to!=f)
		{
			w[i->to]=w[pos]^i->cost,v[i->to]=i->cost,dfs1(i->to,pos,depth+1),siz[pos]+=siz[i->to];
			if(siz[i->to]>maxi) maxi=siz[i->to],hvson[pos]=i->to;
		}
}
inline void dfs2(int pos,int Start)
{
	start[pos]=Start,id[pos]=++times,neww[times]=w[pos];
	if(hvson[pos]) dfs2(hvson[pos],Start);
	for(edge *i=head[pos];i!=nullptr;i=i->next)
		if(i->to!=fa[pos] && i->to!=hvson[pos]) dfs2(i->to,i->to);
}
struct node
{
	int cnt0[10],cnt1[10],lazy[10];
	node *lc,*rc;
	inline node()
	{
		memset(cnt0,0,sizeof(cnt0)),memset(cnt1,0,sizeof(cnt1)),memset(lazy,0,sizeof(lazy));
		lc=rc=nullptr;
	}
	inline void pushup()
	{
		for(int i=0;i<10;i++) cnt0[i]=lc->cnt0[i]+rc->cnt0[i],cnt1[i]=lc->cnt1[i]+rc->cnt1[i];
	}
	inline void pushdown()
	{
		for(int i=0;i<10;i++)
			if(lazy[i])
			{
				std::swap(lc->cnt0[i],lc->cnt1[i]),std::swap(rc->cnt0[i],rc->cnt1[i]);
				lc->lazy[i]^=1,rc->lazy[i]^=1,lazy[i]=0;
			}
	}
};
class seg_tree
{
	#define ls l,mid
	#define rs mid+1,r
private:
	node *root;
	inline node *build(int l,int r)
	{
		node *rt=new node();
		if(l==r) for(int i=0;i<10;i++) rt->cnt1[i]=((neww[l]>>i)&1),rt->cnt0[i]=rt->cnt1[i]^1;
		else
		{
			int mid=(l+r)/2;
			rt->lc=build(ls),rt->rc=build(rs),rt->pushup();
		}
		return rt;
	}
	inline void fix(node *rt,const int L,const int R,int l,int r)
	{
		if(L<=l && r<=R)
		{
			for(int i=0;i<10;i++)
				if(val[i])
					std::swap(rt->cnt0[i],rt->cnt1[i]),rt->lazy[i]^=1;
			return;
		}
		int mid=(l+r)/2;
		rt->pushdown();
		if(L<=mid) fix(rt->lc,L,R,ls);
		if(R>mid) fix(rt->rc,L,R,rs);
		rt->pushup();
	}
	inline node ask(node *rt,const int L,const int R,int l,int r)
	{
		if(L<=l && r<=R) return *rt;
		int mid=(l+r)/2;
		rt->pushdown();
		if(L>mid) return ask(rt->rc,L,R,rs);
		if(R<=mid) return ask(rt->lc,L,R,ls);
		node ll=ask(rt->lc,L,R,ls),rr=ask(rt->rc,L,R,rs),ret;
		ret.lc=&ll,ret.rc=&rr,ret.pushup();
		return ret;
	}
public:
	inline void build()
	{
		root=build(1,n);
	}
	inline void fix(const int L,const int R)
	{
		fix(root,L,R,1,n);
	}
	inline node ask(const int L,const int R)
	{
		return ask(root,L,R,1,n);
	}
};
seg_tree tree;
inline long long ask(int x,int y)
{
	long long ret=0;
	long long cnt0[10],cnt1[10];
	memset(cnt0,0,sizeof(cnt0)),memset(cnt1,0,sizeof(cnt1));
	node tmp;
	while(start[x]!=start[y])
	{
		if(dep[start[x]]<dep[start[y]]) std::swap(x,y);
		tmp=tree.ask(id[start[x]],id[x]),x=fa[start[x]];
		for(int i=0;i<10;i++) cnt0[i]+=tmp.cnt0[i],cnt1[i]+=tmp.cnt1[i];
	}
	if(dep[x]>dep[y]) std::swap(x,y);
	tmp=tree.ask(id[x],id[y]);
	for(int i=0;i<10;i++) cnt0[i]+=tmp.cnt0[i],cnt1[i]+=tmp.cnt1[i];
	for(int i=0;i<10;i++) ret+=cnt0[i]*cnt1[i]*(1ll<<i);
	return ret;
}
int main()
{
	in>>n>>m;
	for(int i=1,x,y,z;i<n;i++) in>>x>>y>>z,add(x,y,z),add(y,x,z);
	dfs1(1,0,1),dfs2(1,1),tree.build();
	for(int i=1,op,x,y,z;i<=m;i++)
	{
		in>>op>>x>>y;
		if(op==1) out<<ask(x,y)<<'\n';
		else if(op==2)
		{
			in>>z;
			if(dep[x]<dep[y]) std::swap(x,y);
			memset(val,0,sizeof(val));
			for(int j=0;j<10;j++) if(((z>>j)&1)!=((v[x]>>j)&1)) val[j]=1;
			tree.fix(id[x],id[x]+siz[x]-1),v[x]=z;
		}
	}
	fwrite(Ouf,1,p3-Ouf,stdout),fflush(stdout);
	return 0;
}
<think>好的,我现在需要帮用户解决P1656炸铁路的问题。用户想要题解、代码、解析和思路。首先,我应该回顾一下题目内容。根据引用[2],题目可能涉及铁路被炸的情况,用户提供的示例代码中有结构体存储铁路的起点、终点和状态,可能是在处理图的连通性问题。 用户提供的引用[2]中有一段代码,看起来像是在处理输入的铁路数据,每条铁路初始状态为未被炸(w=1)。这可能意味着问题需要找出关键边,也就是炸掉后会影响图的连通性的边。这类问题通常可以通过Tarjan算法求桥来解决,因为桥是图中的边,移除后会导致图不再连通。 接下来,我需要确认P1656的具体要求。根据常见的算法题,炸铁路很可能就是寻找无向图中的所有桥。桥的定义是,如果删除这条边,图会被分成两个或多个连通块。Tarjan算法可以在O(V+E)的时间内找到所有桥,这应该是解题的关键。 然后,我需要考虑如何构建图结构。通常,使用邻接表来存储图,每个节点有一个列表记录其连接的边。在实现Tarjan算法时,需要记录每个节点的发现时间和low值,用于判断是否为桥。 接下来,代码实现部分需要包括以下步骤: 1. 读取输入,构建邻接表。 2. 使用Tarjan算法遍历所有节点,找出所有桥。 3. 将找到的桥按题目要求的顺序输出,通常是按端点升序排列,如果端点相同则按另一端升序。 在引用[3]中提到拓扑排序用于车站分级问题,但这里的问题不同,可能需要不同的方法。因此,引用[3]和[1]可能不适用,但引用[2]中的代码结构可能帮助理解输入处理。 用户可能需要详细的步骤解释,比如如何应用Tarjan算法,如何处理输入输出,以及如何排序结果。此外,需要确保代码正确,符合题目的要求,比如边的输出顺序。 最后,验证思路是否正确。例如,Tarjan算法确实用于找桥,而题目要求找出所有这样的边,因此这个方法是合适的。然后,编写代码时需要注意无向图的处理,避免重复访问同一条边,通常通过记录父节点来实现。 总结思路:使用Tarjan算法寻找无向图中的所有桥,并按规则排序输出。</think>### P1656 炸铁路 题解 #### 题目分析 题目要求找到所有「关键铁路」,即删除该铁路后会导致图的连通性被破坏的边。这类问题对应图论中的**桥(割边)**问题,可以通过**Tarjan算法**高效解决[^5]。 #### 算法思路 1. **桥的定义**:无向图中若删除边 $(u,v)$ 后图不再连通,则该边为桥。 2. **Tarjan算法**:通过DFS遍历图,记录每个顶点的访问顺序(`dfn`)和能回溯到的最早祖先(`low`)。若对于边 $(u,v)$,满足 `low[v] > dfn[u]`,则说明 $(u,v)$ 是桥[^5]。 #### 代码实现 ```cpp #include <iostream> #include <vector> #include <algorithm> #include <cstring> using namespace std; const int MAXN = 155; vector<int> graph[MAXN]; vector<pair<int, int>> bridges; int dfn[MAXN], low[MAXN], parent[MAXN]; int timer = 0; void tarjan(int u) { dfn[u] = low[u] = ++timer; for (int v : graph[u]) { if (!dfn[v]) { parent[v] = u; tarjan(v); low[u] = min(low[u], low[v]); if (low[v] > dfn[u]) { // 找到桥 bridges.push_back({min(u, v), max(u, v)}); } } else if (v != parent[u]) { low[u] = min(low[u], dfn[v]); } } } int main() { int n, m; cin >> n >> m; for (int i = 0; i < m; ++i) { int u, v; cin >> u >> v; graph[u].push_back(v); graph[v].push_back(u); } for (int i = 1; i <= n; ++i) { if (!dfn[i]) { tarjan(i); } } sort(bridges.begin(), bridges.end()); for (auto &bridge : bridges) { cout << bridge.first << " " << bridge.second << endl; } return 0; } ``` #### 代码解析 1. **输入处理**:构建无向图的邻接表。 2. **Tarjan核心**:DFS遍历时维护`dfn`和`low`数组,判断桥的条件。 3. **结果排序**:将桥按字典序排序后输出,满足题目要求[^2]。 #### 复杂度分析 - 时间复杂度:$O(N + M)$,其中 $N$ 为顶点数,$M$ 为边数。 - 空间复杂度:$O(N + M)$,用于存储图结构和中间数据。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值