【BHOJ 胡须】树状数组 | 树链剖分 | E

以前看到这道题不知所云,今天终于把它A啦…

【BHOJ 329】胡须

时间限制: 2000 ms 内存限制: 65536 kb
总通过人数: (未加载)总提交人数: (未加载)

Tags:树链剖分 树状数组


题目描述

一棵树是胡须树当且仅当除根结点外的结点都只有一个儿子。胡须树的每条边要么是黑色要么是白色。

给定一棵胡须树,树结点数 V ∈ [ 2 , 1 0 5 ] V \in [2, 10^5] V[2,105]。有两种操作(操作次数 q ∈ [ 1 , 1 0 5 ] q\in[1, 10^5] q[1,105]):

  • 1.把第 i i i 条边(按输入顺序,显然 i ∈ [ 1 , V − 1 ] i \in [1, V-1] i[1,V1])的颜色反转,不作任何输出。
  • 2.查询 u , v u, v u,v 两结点的最短路。如果该最短路上的边都是黑色,则输出最短路径长度;否则输出 − 1 -1 1



输入样例1

3
1 2
2 3
7
3 1 2
3 1 3
3 2 3
2 2
3 1 2
3 1 3
3 2 3

输出样例1

1
2
1
1
-1
-1


输入样例2

6
1 5
6 4
2 3
3 5
5 6
6
3 3 4
2 5
3 2 6
3 1 2
2 3
3 3 1

输出样例2

3
-1
3
2



分析


啊哈哈,当初看到这道题的时候还无从下手,现在发现其实就是一个弱化版的边权树剖

不必动用两次dfs,直接把胡须树的每条胡须当成链就行啦。

然后这里是单点修改+区间查询,那当然是上树状数组~

另外一些小细节注意一下就行(给边编号啥的)


时间复杂度:
  • 找根(入度最大的), O ( V ) O(V) O(V)
  • 编dfs序 + 标记top, O ( V ) O(V) O(V)
  • 每次更新和查询,都是 O ( l o g V ) O(log V) O(logV)
  • 总时间复杂度 O ( q   l o g V ) O(q\ log V) O(q logV)



AC代码

#include <cstdio>

#define MAX(a, b) ((a)>(b)?(a):(b))
#define SWAP(a, b) do{auto _t=a; a=b; b=_t;}while(0)

constexpr int MN(1e5+7);
enum Color
{
	BLACK, WHITE
};


struct Ed
{
	int u, v;
	Ed *next;
} ed[MN << 1], *head[MN];
int tot;
#define edd(uu, vv) ed[++tot].next=head[uu], ed[tot].u=uu, ed[tot].v=vv, head[uu]=tot+ed


int deg[MN];		// 入度,用来找根(入度最大的)
int top_color[MN];	// 存储top结点和根结点之间的边的颜色。边的编号是所连接两点编号的更大者(根编号是0)。
int color[MN];		// 存储其他普通边的颜色。边的编号是所连接两点编号的更大者。

int now_top;
int top[MN], id[MN], time;
void dfs(const int u, const int fa)
{
	top[u] = now_top;
	id[u] = ++time;
	for (Ed *p=head[u]; p; p=p->next)
		if (p->v != fa)
			dfs(p->v, u);
}


namespace BIT
{
inline constexpr int lowbit(const int x)
{
	return x & -x;
}

int b[MN];
inline void add(int i, const int v)
{
	while (i <= time)
		b[i] += v, i += lowbit(i);
}
inline int sum(int i)
{
	int s = 0;
	while (i > 0)
		s += b[i], i -= lowbit(i);
	return s;
}
inline int sum(const int l, const int r)
{
	return sum(r) - sum(l-1);
}

}


int main()
{
	int V;
	scanf("%d", &V);
	int root = 0;
	int u, v;

	while (--V)
	{
		scanf("%d %d", &u, &v);
		edd(v, u);
		edd(u, v);
		if (++deg[u] > deg[root])
			root = u;
		if (++deg[v] > deg[root])
			root = v;
	}

	for (Ed *p=head[root]; p; p=p->next)
	{
		now_top = p->v;
		dfs(p->v, root);	// 对胡须树的一条胡须链标id号和top
	}

	int q;
	scanf("%d", &q);
	while (q--)
	{
		int op;
		scanf("%d", &op);
		if (op == 3)
		{
			int color_sum = 0, dis = 0;
			scanf("%d %d", &u, &v);
			if (u == v)
				puts("0");
			else
			{
				if (top[u]==top[v])
				{
					int idu = id[u], idv = id[v];
					if (idu > idv)
						SWAP(idu, idv);
					color_sum = BIT::sum(idu+1, idv), dis = idv - idu;	// idu加一是因为树编号的方式是取连接两点的编号更大者
				}
				else
				{
					if (u != root)
						color_sum += BIT::sum(id[top[u]], id[u]) + top_color[u], dis += id[u] - id[top[u]] + 1;
					if (v != root)
						color_sum += BIT::sum(id[top[v]], id[v]) + top_color[v], dis += id[v] - id[top[v]] + 1;
				}

				if (color_sum)
					puts("-1");
				else
					printf("%d\n", dis);
			}
		}
		else
		{
			int k;
			scanf("%d", &k);
			const auto &edge = ed[k << 1];	// 双向边,边的实际编号是逻辑编号*2
			if (edge.u==root)
				top_color[edge.v] ^= 1;
			else if (edge.v==root)
				top_color[edge.u] ^= 1;
			else
			{
				const int ed_id = MAX(id[edge.u], id[edge.v]);
				BIT::add(ed_id, color[ed_id] == BLACK ? +1 : -1);
				color[ed_id] ^= 1;
			}
		}
	}

	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值