【BUAA Spring Training 01】并查集+DAG最长路 | Splay/Treap | 堆 | 树链剖分

第一场春训,谢谢lzy、pmxm、jsh等大大 qwq!
谨以E题纪念我的第一次树链剖分…


【BUAA Spring Training 01】


Tags:DAG最长路(拓扑排序) 离散化 并查集 平衡树 线段树




Problem A. 括号序列计分


[A] 题意

给定一个保证正常匹配的小括号序列(长度 ∈ [ 1 , 100000 ] \in [1, 100000] [1,100000])。定义得分:

  • ( ) () () 的得分是 1
  • A A A 是非空括号序列,得分是 k k k,则 ( A ) (A) (A) 得分是 2 × k 2 \times k 2×k
  • A 、 B A、B AB 都是非空括号序列,得分分别是 k a 、 k b k_a、k_b kakb,则 A B AB AB 得分是 k a + k b k_a+k_b ka+kb

输出该序列的得分(对 12345678910 12345678910 12345678910 取模)


[A] 思路

递归定义,递归解决。

时间复杂度: O ( n ) O(n) O(n)



[A] 代码

/*
 *      If we give,
 *      all we've got,
 *      we will make it through.
 */


#include <cstdio>
#define LL long long

constexpr int MN(1e5+7);
constexpr LL MOD(12345678910LL);

char s[MN], *now = s;

LL get_term(void);
LL get_expr(void);

int main()
{
	int N, tp;
	scanf("%d", &N);
	for (int i=0; i<N; ++i)
	{
		scanf("%d", &tp);
		s[i] = tp ? ')' : '(';
	}
	printf("%lld", get_expr());

	return 0;
}

LL get_term(void)
{
	LL ret = 0;
	if (*now != '(')
		return ret;

	++now, ret = get_expr(), ++now;
	return ret ? (ret << 1) % MOD : 1;
}

LL get_expr(void)
{
	LL sum = 0;
	while (*now == '(')
		sum = (sum + get_term() % MOD) % MOD;
	return sum;
}





Problem B. 冰镇矩阵


[B] 题意

给定一个 R R R C C C 列( R × C ∈ [ 1 , 100000 ] R \times C \in [1, 100000] R×C[1,100000])的矩阵,

要把这个矩阵的值离散化,具体要求:

  • 同行 or 同列中相等的值在离散化后仍然相等
  • 同行 or 同列中不等的值之间的不等关系在离散化后仍然保持

输出离散化后的矩阵中的最大值。


[B] 思路

其实就是求一个不等关系最长链。对应解法就是先把逐行把同行中的相等的值对应的结点给连起来(相当于缩点。利用并查集即可),然后逐列作相同处理。

然后开始建图。先逐行遍历,把缩点后的、具有不等关系的结点之间连一条有向边。然后逐列作相同处理。
然后就在建出来的 DAG 上跑最长路(拓扑排序即可)。最长路上的结点数就是答案。

时间复杂度: O ( ( n ∗ m ) log ⁡ ( n ∗ m ) ) O((n*m)\log(n*m)) O((nm)log(nm))


然鹅逐行排序、逐列排序真的好烦啊… (于是跑去CF看了下前几名的解法 qwq)发现了大神们的另一种解法:直接全员排序,然后贪心地从全局最小值开始考虑,直到最大值。当然,答案显然不是通过简单的 sort->unique->erase 就能得到的,而还是要用并查集维护一下同行同列相同的值。

时间复杂度: O ( ( n ∗ m ) log ⁡ ( n ∗ m ) ) O((n*m)\log(n*m)) O((nm)log(nm))



[B] 代码


法一,建图跑拓扑排序:

#include <bits/stdc++.h>

#define GC getchar()
#define _SN(x) {char _c=GC,_v=1;for(x=0;_c<48||_c>57;_c=GC)if(_c==45)_v=-1;for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=GC);if(_v==-1)x=-x;}
#define _SAN(a,n) {auto _i=0,_n=n;for(;_i<_n;++_i)_SN(a[_i])}
#define _SA(a,l,r) {auto _i=l,_r=r;for(;_i<_r;++_i)_SN(a[_i])}
#define _gS(_1, _2, _3, _sc, ...) _sc
#define sc(...) _gS(__VA_ARGS__,_SA,_SAN,_SN, ...)(__VA_ARGS__)
#define _G1(_1) int _1;sc(_1)
#define _G2(_1,_2) int _1,_2;sc(_1)sc(_2)
#define _G3(_1,_2,_3) int _1,_2,_3;sc(_1)sc(_2)sc(_3)
#define _gG(_1,_2,_3,_get, ...) _get
#define get(...) _gG(__VA_ARGS__,_G3,_G2,_G1, ...)(__VA_ARGS__)
#define _F0N(i,n) for(auto i=0;i<n;++i)
#define _FLR(i,l,r) for(auto i=l,_r=r;i<_r;++i)
#define _gF(_1, _2, _3, _F, ...) _F
#define F(...) _gF(__VA_ARGS__,_FLR,_F0N, ...)(__VA_ARGS__)
#define _FD0(i,n) for(auto i=0;i<=n;++i)
#define _FDL(i,l,r) for(auto i=l,_r=r;i<=_r;++i)
#define _gFD(_1, _2, _3, _FD, ...) _FD
#define FD(...) _gFD(__VA_ARGS__,_FDL,_FD0, ...)(__VA_ARGS__)
#define FED(src) for (Ed *p=head[src]; p; p=p->next)

#define OPER1(T,x1,b1) inline bool operator<(const T&_o)const{return x1 b1 _o.x1;}
#define OPER2(T,x1,b1,x2,b2) inline bool operator<(const T&_o)const{return x1 b1 _o.x1||x1==_o.x1&&x2 b2 _o.x2;}
#define OPER3(T,x1,b1,x2,b2,x3,b3) inline bool operator<(const T&_o)const{return x1 b1 _o.x1||x1==_o.x1&&(x2 b2 _o.x2||x2==_o.x2&&x3 b3 _o.x3);}

#define IL inline
#define LL long long
#define ULL unsigned LL
#define PC putchar
template<class T>
void PRT(const T _){if(_<0){PC(45),PRT(-_);return;}if(_>=10)PRT(_/10);PC(_%10+48);}
template<class T>
void UPRT(const T _){if(_>=10)UPRT(_/10);PC(_%10+48);}

#define CON constexpr
#define T_CASE int T;sc(T)for(int CASE=1;CASE<=T;++CASE)
#define Tjj int T;sc(T)while(T--)
#define qjj int q;sc(q)while(q--)
#define cincout std::cin.tie(nullptr),std::cout.tie(nullptr),std::ios::sync_with_stdio(false);
#define EPS 1e-8
#define PI 3.141592653589793
#define MAX_INT 2147483647
#define MIN_INT -2147483648
#define MAX_LL 9223372036854775807LL
#define MIN_LL -9223372036854775808LL
#define INF 0x3f3f3f3f
#define LINF 0x3f3f3f3f3f3f3f3fLL
#define priority_queue priority_queue
#define PQ std::priority_queue
#define PR std::pair
#define vector vector
#define unordered_ unordered_
#define VI std::vector<int>
#define MII std::map<int,int>
#define MLI std::map<LL,int>
#define MSI std::map<std::string,int>
#define PII std::pair<int,int>
#define PLI std::pair<LL,int>
#define PSI std::pair<std::string,int>
#define MPFD(k) auto it=mp.find(k)

#define MIN(a, b) ((a)<(b)?(a):(b))
#define MIN3(a, b, c) (MIN(a, MIN(b, c)))
#define MAX(a, b) ((a)>(b)?(a):(b))
#define MAX3(a, b, c) (MAX(a, MAX(b, c)))
#define ABS(a) ((a)>0?(a):-(a))
#define FABS(a) ((a)>0?(a):-(a))
#define log2n(x) (log(x)/0.69314718055995)
#define MHD(p1, p2) ((p1.x>p2.x?p1.x-p2.x:p2.x-p1.x)+(p1.y>p2.y?p1.y-p2.y:p2.y-p1.y))

#define PB emplace_back
#define EB emplace_back
#define BRK else break
#define ALL(X) (X).begin(),(X).end()
#define SORT(X) std::sort(ALL(X))
#define SORTD(X) std::sort(ALL(X),std::greater<decltype((X)[0])>())
#define SWAP(a, b) do{auto _t=a; a=b; b=_t;}while(0)
#define mem0(a) memset(a,0,sizeof(a))
#define memf1(a) memset(a,-1,sizeof(a))
#define meminf(a) memset(a,0x3f,sizeof(a))

CON int MN(1e6+7), MV(MN), ME(MV << 1);


struct Elem
{
	int val;
	int id;
};
struct Iter
{
	Elem *ptr;

	inline Iter & operator =(Elem *ptr)
	{
		this->ptr = ptr;
		return *this;
	}
	inline Elem * operator ->(void)
	{
		return ptr;
	}
	inline bool operator <(const Iter o) const
	{
		return ptr->val < o.ptr->val;
	}
};
std::vector<Elem> a[MN];
Iter iter[MN];


namespace UF
{
int uf[MV];
inline void init(const int V)
{
	memset(uf, -1, (V+1) * sizeof(*uf));
}

int find(const int x)
{
	if (uf[x] >= 0)
		return uf[x] = find(uf[x]);
	return x;
}

inline void merge(int x, int y)
{
	x = find(x), y = find(y);
	if (x < y)
		uf[x] += uf[y], uf[y] = x;
	else if(y < x)
		uf[y] += uf[x], uf[x] = y;
}
};


struct Ed
{
	int v;
	Ed *next;
} ed[ME], *head[MV];
int tot;
int ind[MV];
inline void edd(const int u, const int v)
{
	ed[++tot].next = head[u],
	ed[tot].v = v,
	head[u] = ed+tot,
	++ind[v];
}

struct Node
{
	int v;
	int dep;
} q[MV];


void merge_same_vals(const int R, const int C);
void build_graph(const int R, const int C);
int topo_sort(const int V);


int main()
{
	get(R, C)
	int cnt = 0;
	F(r, R)
	{
		a[r].resize(C);	// 不开O2的话,reserve更快,但有风险。reserve不改变.size(),意味着大小我们自己要知道。另外reserve不对allocate到的空间进行初始化。
		F(c, C)
		{
			sc(a[r][c].val)
			a[r][c].id = ++cnt;
		}
	}

	merge_same_vals(R, C);
	build_graph(R, C);
	UPRT(topo_sort(R*C));

	return 0;
}


void merge_same_vals(const int R, const int C)
{
	UF::init(R*C);

	F(r, R)
	{
		F(c, C)
			iter[c] = &a[r][c];
		std::sort(iter, iter+C);	// 就地排序
		F(c, 1, C)
			if (iter[c-1]->val == iter[c]->val)
				UF::merge(iter[c-1]->id, iter[c]->id);	// 合并同行等值的
	}
	F(c, C)
	{
		F(r, R)
			iter[r] = &a[r][c];
		std::sort(iter, iter+R);	// 就地排序
		F(r, 1, R)
			if (iter[r-1]->val == iter[r]->val)
				UF::merge(iter[r-1]->id, iter[r]->id);	// 合并同列等值的
	}
}

void build_graph(const int R, const int C)
{
	F(r, R)
	{
		F(c, C)
			iter[c] = &a[r][c];
		std::sort(iter, iter+C);	// 就地排序
		F(c, 1, C)
			if (iter[c-1]->val != iter[c]->val)
				edd(UF::find(iter[c-1]->id), UF::find(iter[c]->id));	// 建边
	}
	F(c, C)
	{
		F(r, R)
			iter[r] = &a[r][c];
		std::sort(iter, iter+R);	// 就地排序
		F(r, 1, R)
			if (iter[r-1]->val != iter[r]->val)
				edd(UF::find(iter[r-1]->id), UF::find(iter[r]->id));	// 建边
	}
}

int topo_sort(const int V)
{
	int hd = 0, tl = 0;
	FD(v, 1, V)
		if (UF::uf[v]<0 && !ind[v])
			q[tl].v = v, q[tl++].dep = 1;

	while (hd != tl)
	{
		const Node &now = q[hd++];
		FED(now.v)
			if (!--ind[p->v])
				q[tl].v = p->v, q[tl++].dep = now.dep+1;
	}

	return q[tl-1].dep;
}


法二:贪心,直接维护并查集

#include <bits/stdc++.h>

#define GC getchar()
#define _SN(x) {char _c=GC,_v=1;for(x=0;_c<48||_c>57;_c=GC)if(_c==45)_v=-1;for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=GC);if(_v==-1)x=-x;}
#define _SAN(a,n) {auto _i=0,_n=n;for(;_i<_n;++_i)_SN(a[_i])}
#define _SA(a,l,r) {auto _i=l,_r=r;for(;_i<_r;++_i)_SN(a[_i])}
#define _gS(_1, _2, _3, _sc, ...) _sc
#define sc(...) _gS(__VA_ARGS__,_SA,_SAN,_SN, ...)(__VA_ARGS__)
#define _G1(_1) int _1;sc(_1)
#define _G2(_1,_2) int _1,_2;sc(_1)sc(_2)
#define _G3(_1,_2,_3) int _1,_2,_3;sc(_1)sc(_2)sc(_3)
#define _gG(_1,_2,_3,_get, ...) _get
#define get(...) _gG(__VA_ARGS__,_G3,_G2,_G1, ...)(__VA_ARGS__)
#define _F0N(i,n) for(auto i=0;i<n;++i)
#define _FLR(i,l,r) for(auto i=l,_r=r;i<_r;++i)
#define _gF(_1, _2, _3, _F, ...) _F
#define F(...) _gF(__VA_ARGS__,_FLR,_F0N, ...)(__VA_ARGS__)
#define _FD0(i,n) for(auto i=0;i<=n;++i)
#define _FDL(i,l,r) for(auto i=l;i<=r;++i)
#define _gFD(_1, _2, _3, _FD, ...) _FD
#define FD(...) _gFD(__VA_ARGS__,_FDL,_FD0, ...)(__VA_ARGS__)
#define FED(src) for (Ed *p=head[src]; p; p=p->next)

#define OPER1(T,x1,b1) inline bool operator<(const T&_o)const{return x1 b1 _o.x1;}
#define OPER2(T,x1,b1,x2,b2) inline bool operator<(const T&_o)const{return x1 b1 _o.x1||x1==_o.x1&&x2 b2 _o.x2;}
#define OPER3(T,x1,b1,x2,b2,x3,b3) inline bool operator<(const T&_o)const{return x1 b1 _o.x1||x1==_o.x1&&(x2 b2 _o.x2||x2==_o.x2&&x3 b3 _o.x3);}

#define IL inline
#define LL long long
#define ULL unsigned LL
#define PC putchar
template<class T>
void PRT(const T _){if(_<0){PC(45),PRT(-_);return;}if(_>=10)PRT(_/10);PC(_%10+48);}
template<class T>
void UPRT(const T _){if(_>=10)UPRT(_/10);PC(_%10+48);}

#define CON constexpr
#define T_CASE int T;sc(T)for(int CASE=1;CASE<=T;++CASE)
#define Tjj int T;sc(T)while(T--)
#define qjj int q;sc(q)while(q--)
#define cincout std::cin.tie(nullptr),std::cout.tie(nullptr),std::ios::sync_with_stdio(false);
#define EPS 1e-8
#define PI 3.141592653589793
#define MAX_INT 2147483647
#define MIN_INT -2147483648
#define MAX_LL 9223372036854775807LL
#define MIN_LL -9223372036854775808LL
#define INF 0x3f3f3f3f
#define LINF 0x3f3f3f3f3f3f3f3fLL
#define priority_queue priority_queue
#define PQ std::priority_queue
#define PR std::pair
#define vector vector
#define unordered_ unordered_
#define VI std::vector<int>
#define MII std::map<int,int>
#define MLI std::map<LL,int>
#define MSI std::map<std::string,int>
#define PII std::pair<int,int>
#define PLI std::pair<LL,int>
#define PSI std::pair<std::string,int>
#define MPFD(k) auto it=mp.find(k)

#define MIN(a, b) ((a)<(b)?(a):(b))
#define MIN3(a, b, c) (MIN(a, MIN(b, c)))
#define MAX(a, b) ((a)>(b)?(a):(b))
#define MAX3(a, b, c) (MAX(a, MAX(b, c)))
#define ABS(a) ((a)>0?(a):-(a))
#define FABS(a) ((a)>0?(a):-(a))
#define log2n(x) (log(x)/0.69314718055995)
#define MHD(p1, p2) ((p1.x>p2.x?p1.x-p2.x:p2.x-p1.x)+(p1.y>p2.y?p1.y-p2.y:p2.y-p1.y))

#define PB emplace_back
#define EB emplace_back
#define BRK else break
#define ALL(X) (X).begin(),(X).end()
#define SORT(X) std::sort(ALL(X))
#define SORTD(X) std::sort(ALL(X),std::greater<decltype((X)[0])>())
#define SWAP(a, b) do{auto _t=a; a=b; b=_t;}while(0)
#define mem0(a) memset(a,0,sizeof(a))
#define memf1(a) memset(a,-1,sizeof(a))
#define meminf(a) memset(a,0x3f,sizeof(a))

CON int MN(1e6+7), MV(MN), ME(MV << 1);


struct Elem
{
	int val;
	int r, c, pos;
	int id;
} a[MN];
struct Iter
{
	Elem *ptr;

	inline Iter & operator =(Elem *ptr)
	{
		this->ptr = ptr;
		return *this;
	}
	inline Elem * operator ->(void)
	{
		return ptr;
	}
	inline bool operator <(const Iter o) const
	{
		return ptr->val < o.ptr->val;
	}
} iter[MN];

namespace UF
{
int uf[MV];

int find(const int x)
{
	return uf[x]==x ? x : uf[x]=find(uf[x]);
}
};


int last_r_pos[MN];
int last_c_pos[MN];

int main()
{
	_IO
	get(R, C)

	int tot = 0;
	FD(r, 1, R)
	{
		FD(c, 1, C)
		{
			++tot;
			sc(a[tot].val)
			a[tot].r = r, a[tot].c = c, a[tot].pos = UF::uf[tot] = tot,
			iter[tot] = a+tot;
		}
	}
	std::sort(iter+1, iter+tot+1);

	int id1, id2, max_id = 0;
	FD(i, 1, tot)
	{
		Iter p = iter[i];
		const int last_r_p = UF::find(last_r_pos[p->c]);	// 在第c行相同的数中,最靠后的位置是last_r_p(其具有最新的id)
		const int last_c_p = UF::find(last_c_pos[p->r]);	// 在第r列相同的数中,最靠后的位置是last_c_p(其具有最新的id)

		if (p->val > a[last_r_p].val)
			id1 = a[last_r_p].id + 1;
		else
		{
			id1 = a[last_r_p].id;
			if (p->val == a[last_r_p].val)
				UF::uf[last_r_p] = p->pos;	// 合并同行的等值点。只有把p->pos设为根结点,才能保证等值点的集合的根是pos最大者
		}

		if (p->val > a[last_c_p].val)
			id2 = a[last_c_p].id + 1;
		else
		{
			id2 = a[last_c_p].id;
			if (p->val == a[last_c_p].val)
				UF::uf[last_c_p] = p->pos;	// 合并同列的等值点。只有把p->pos设为根结点,才能保证等值点的集合的根是pos最大者
		}

		p->id = MAX(id1, id2);
		max_id = MAX(p->id, max_id);
		last_r_pos[p->c] = last_c_pos[p->r] = p->pos;	// 保存刚才在第c行、第r列填的位置(显然这个位置不一定是与该pos值相同的、在同行or同列的数的集合中,位置最靠后的。所以才需要并查集)
														// 存这个值是为了通过并查集能找到与该pos值相同的、在同行or同列的数的集合中,位置最靠后的在哪 
	}

	UPRT(max_id);
//	for (int i=1; i<=tot; PC(10))
//		for (int c=0; c<C; ++c, ++i)
//			UPRT(a[UF::find(i)].id), PC(32);

	return 0;
}




Problem C. 斯波利特平衡术


[C] 题意

给定序列的长度 n n n,初始时整个序列是 [ 1 , 2 , . . . , n ] [1, 2, ..., n] [1,2,...,n]

k k k 次操作,每次操作给出两个整数 l , r l, r l,r,然后将此区间左右翻转。

要求每次操作后输出该区间的数之和,然后所有操作完成后输出最后的区间。

范围: 1 ≤ n ≤ 1 0 5 , 1 ≤ k ≤ 1 0 5 , 1 ≤ l ≤ r ≤ n 1\le n\le 10^5, 1\le k\le 10^5,1\le l \le r\le n 1n1051k1051lrn


[C] 思路

FHQ Treap / Splay 模板题…(本渣也是刚学w

关于 FHQ Treap 的详解,可以戳这里:【平衡树总结Ⅲ】【FHQ Treap】非旋Treap | E


[C] 代码

#include <cstdio>
#include <random>

constexpr int MN(1e5+7);

template <typename vint, typename sint>
class FHQ
{
private:

	using rint = unsigned long;
	using xint = int;

	std::mt19937 rander;
	struct Node
	{
		Node *lc, *rc;
		vint v;
		sint s;
		rint w;
		xint sz;
		bool reved;

		void rev(void)
		{
			reved = !reved;
			Node *tp = lc;
			lc = rc;
			rc = tp;
		}
	} _pool[MN], *pool=_pool;
	Node _NIL = {&_NIL, &_NIL, 0, 0, 0, 0, false}, *const NIL = &_NIL;
	Node *root = NIL;
	Node *new_node(const vint v)
	{
		return *++pool = {NIL, NIL, v, v, rander(), 1, false}, pool;
	}

#define pu(p) \
	({ \
		p->s = p->lc->s + p->rc->s + p->v, \
		p->sz = p->lc->sz + p->rc->sz + 1; \
	})

#define pd(p) \
	({ \
		if (p->reved) \
		{ \
			p->lc->rev(), p->rc->rev(), \
			p->reved = false; \
		} \
	})

	void mg(Node *&rt, Node *l, Node *r)
	{
		if (l == NIL)
			rt = r;
		else if (r == NIL)
			rt = l;
		else if (l->w < r->w)
			rt = l, pd(rt), mg(rt->rc, rt->rc, r), pu(rt);
		else
			rt = r, pd(rt), mg(rt->lc, l, rt->lc), pu(rt);
	}
	void sp(Node *rt, Node *&l, Node *&r, const xint k)
	{
		if (rt == NIL)
			l = r = NIL;
		else if (k == 0)
			l = NIL, r = rt;
		else if (rt->lc->sz+1 <= k)
			l = rt, pd(rt), sp(rt->rc, rt->rc, r, k-rt->lc->sz-1), pu(rt);
		else
			r = rt, pd(rt), sp(rt->lc, l, rt->lc, k), pu(rt);
	}

public:

	FHQ(void) : rander(19937) { }

	void clear(void)
	{
		pool = _pool;
		root = NIL;
	}

	void insert(const vint v)
	{
		mg(root, root, new_node(v));
	}

	sint rev(xint l, xint r)
	{
		Node *x, *y, *z;
		sp(root, x, y, l-1);
		sp(y, y, z, r-l+1);

		// roots:            x            y            z
		// intervals:    [ 1, l-1 ]   [ l,   r ]   [ r+1, n ]
		// size:            l-1         r-l+1         n-r
		y->rev();
		sint s = y->s;

		mg(y, y, z);
		mg(root, x, y);

		return s;
	}

	void dfs(Node *p)
	{
		if (p == NIL)
			return;
		pd(p);
		dfs(p->lc), printf("%d ", p->v), dfs(p->rc);
	}
	void dfs(void)
	{
		dfs(root);
	}
};


FHQ<int, long long> h;

int main()
{
	int n, k, l, r;
	scanf("%d %d", &n, &k);
	for (int i=1; i<=n; ++i)
		h.insert(i);

	while (k--)
	{
		scanf("%d %d", &l, &r);
		printf("%lld\n", h.rev(l, r));
	}
	h.dfs();

	return 0;
}




Problem D. 前 k 小和



Problem E. npm - Node.js


[E] 题意

给定一棵根结点编号为 0 0 0 的树。初始点权都是 0 0 0,有两种操作:

  • ① 查询结点到根的路径上有多少个值为 0 0 0 的点,然后路径上点权全部赋 1 1 1
  • ② 查询某个结点的子树有多少个值为 1 1 1 的点,然后子树结点点权全部赋 0 0 0

范围: 1 ≤ n ≤ 1 0 5 , 1 ≤ q ≤ 1 0 5 1\le n\le 10^5, 1\le q\le 10^5 1n1051q105


[E] 思路

树剖 模板题…(可是本渣才刚学这个w

树剖就相当于是把一棵树给重新编号(重儿子优先的DFS序),然后再拿一棵线段树去维护这棵树的点权(实际上是维护一条条剖出来的重链,因为同一条链上的编号是连续的(优先遍历重儿子)。当然同时也可以顺带维护子树,因为子树的DFS序也是连续的)

因此我们就可以在 O ( l o g 2 ( n ) ) O(log^2(n)) O(log2(n)) 内完成每次树上简单路径点权更新/查询,或在 O ( l o g ( n ) ) O(log(n)) O(log(n)) 内完成子树点权更新/查询 了。

注意在处理链的时候,如果两个点不在一条重链上,那就得跳跃几次。每次跳的时候应该是更低的先跳,这样才能缩短他们的距离,或者说才能尽可能快地跳到他们的最近公共重链祖先上。


是不是很简单呢?就是代码量稍微有点大…


哦对了还有一点,就是这里有区间赋 0 0 0 操作,所以线段树懒标记得改一改。懒标记是 − 1 -1 1 的时候才认为是无效的哦。



[E] 代码

/*
 *      If we give,
 *      all we've got,
 *      we will make it through.
 */


#include <cstdio>
#include <cstdlib>
#include <climits>

#define GC getchar()
#define _SN(x) {char _c=GC,_v=1;for(x=0;_c<48||_c>57;_c=GC)if(_c==45)_v=-1;for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=GC);if(_v==-1)x=-x;}
#define _SAN(a,n) {auto _i=0,_n=n;for(;_i<_n;++_i)_SN(a[_i])}
#define _SA(a,l,r) {auto _i=l,_r=r;for(;_i<_r;++_i)_SN(a[_i])}
#define _gS(_1, _2, _3, _sc, ...) _sc
#define sc(...) _gS(__VA_ARGS__,_SA,_SAN,_SN, ...)(__VA_ARGS__)
#define _G1(_1) int _1;sc(_1)
#define _G2(_1,_2) int _1,_2;sc(_1)sc(_2)
#define _G3(_1,_2,_3) int _1,_2,_3;sc(_1)sc(_2)sc(_3)
#define _gG(_1,_2,_3,_get, ...) _get
#define get(...) _gG(__VA_ARGS__,_G3,_G2,_G1, ...)(__VA_ARGS__)
#define F(i, l, r) for(int i=l; i<r; ++i)

#define IL inline
#define PC putchar
template<class T>
void PRT(const T _){if(_<0){PC(45),PRT(-_);return;}if(_>=10)PRT(_/10);PC(_%10+48);}
template<class T>
void UPRT(const T _){if(_>=10)UPRT(_/10);PC(_%10+48);}

#define CON constexpr
#define qjj int q;sc(q)while(q--)
#define SWAP(a, b) do{auto _t=a; a=b; b=_t;}while(0)


CON int MN(1e5+7);
CON int MV(MN), ME(MV*2);
struct Edge
{
	int next, v;
} ed[ME];
int head[MV], tot;
#define edd(uu, vv) ed[++tot].next=head[uu], ed[tot].v=vv, head[uu]=tot



CON int ROOT(0);

int de[MV], fa[MV], sz[MV], son[MV];
void dfs1(const int u)
{
	int msz = -1;
	for (int i=head[u]; i; i=ed[i].next)
	{
		const int v = ed[i].v;
		if (v != fa[u])
		{
			de[v] = de[u] + 1,
			fa[v] = u,
			sz[v] = 1,
			dfs1(v),
			sz[u] += sz[v];
			
			if (sz[v] > msz)
			{
				msz = sz[v],
				son[u] = v;
			}
		}
	}
}

int a0[MV];	// 原来树结点的点权(本题全是0)
int top[MV], id[MV], dnt;
int a[MV];	// 线段树要维护的点权
void dfs2(const int u)
{
	if (son[u])
	{
		const int v = son[u];
		top[v] = top[u],	// 一脉相承
		id[v] = ++dnt,
		a[dnt] = a0[v],
		dfs2(v);
	}

	for (int i=head[u]; i; i=ed[i].next)
	{
		const int v = ed[i].v;
		if (v != fa[u] && v != son[u])
		{
			top[v] = v,		// v是新链的根
			id[v] = ++dnt,
			a[dnt] = a0[v],
			dfs2(v);
		}
	}
}



template <typename vint, typename sint, typename xint = int>
class STree
{
#define MMAX INT_MAX
#define MMIN INT_MIN
private:
	static constexpr int ROOT = 1;
	struct Node
	{
		xint l, r;
		sint s, lz;		// 【注意】本题懒标记为0的时候也是有意义的(区间赋0),故应把懒标记的无效值设为-1
	} t[MN << 2];
	vint *a;

	xint ll, rr;
	vint vv;

// 计算管辖区间中点,以及左右儿子
#define get_mlr(i) \
	xint mid = (t[i].l+t[i].r) >> 1, li = i+i, ri = li+1

// 结点增值(不一定非得是叶节点哦)
#define set_v(i, v) \
	do \
	{ \
		t[i].s = (sint)(v) * (t[i].r-t[i].l+1), \
		t[i].lz = v; \
	} while (0)

// 下传懒标记(向下更新/询问之前,要先下传懒标记哦)
#define push_down(i, li, ri) \
	do \
	{ \
		sint lz = t[i].lz; \
		if (~lz) \
		{ \
			set_v(li, lz); \
			set_v(ri, lz); \
			t[i].lz = -1; \
		} \
	} while (0)

// 用子结点的值求父节点的值(更新之后才需要push_up)
#define push_up(i, li, ri) \
	do \
	{ \
		t[i].s = t[li].s + t[ri].s; \
	} while (0)


	void build(const xint i, const xint l, const xint r)
	{
		t[i].l = l, t[i].r = r, t[i].lz = -1;
		if (l == r)
		{
			t[i].s = a[r];
		}
		else
		{
			get_mlr(i);
			build(li, l, mid);
			build(ri, mid+1, r);
			push_up(i, li, ri);	// 更新了,需要push_up
		}
	}

	void set(const xint i)
	{
		if (ll <= t[i].l && t[i].r <= rr)
			set_v(i, vv);
		else
		{
			get_mlr(i);
			push_down(i, li, ri);
			if (ll <= mid)	// 待更新区间和li管辖的区间有交集
				set(li);
			if (rr > mid)	// 待更新区间和ri管辖的区间有交集
				set(ri);
			push_up(i, li, ri);	// 更新了,需要push_up
		}
	}

	sint sum(const xint i)
	{
		if (ll <= t[i].l && t[i].r <= rr)
			return t[i].s;
		else
		{
			get_mlr(i);
			push_down(i, li, ri);
			sint s = 0;
			if (ll <= mid)
				s += sum(li);
			if (rr > mid)
				s += sum(ri);
			// 不用push_up,因为当初打懒标记的时候就已经更新了
			return s;
		}
	}

public:

	IL void build(vint *arr, const xint l, const xint r)
	{
		a = arr;
		build(ROOT, l, r);
	}

	IL void set(const xint l, const xint r, const vint v)
	{
		ll = l, rr = r, vv = v,
		set(ROOT);
	}

	IL sint sum(const xint l, const xint r)
	{
		ll = l, rr = r;
		return sum(ROOT);
	}

};



STree<int, int> st;

namespace SP
{
void set(int u, const int val)
{
	st.set(id[u], id[u] + sz[u] - 1, val);
}
int sum(int u)
{
	return st.sum(id[u], id[u] + sz[u] - 1);
}

void set(int u, int v, const int val)
{
	while (top[u] != top[v])
	{
		if (de[top[u]] > de[top[v]])
			SWAP(u, v);
		st.set(id[top[v]], id[v], val);
		v = fa[top[v]];
	}
	if (de[u] > de[v])
		SWAP(u, v);
	st.set(id[u], id[v], val);
}
int sum(int u, int v)
{
	int s = 0;
	while (top[u] != top[v])
	{
		if (de[top[u]] > de[top[v]])
			SWAP(u, v);
		s += st.sum(id[top[v]], id[v]);
		v = fa[top[v]];
	}
	if (de[u] > de[v])
		SWAP(u, v);
	s += st.sum(id[u], id[v]);
	
	return s;
}

}



int main()
{
#ifdef _VSC_KEVIN
	freopen("in.in", "r", stdin);
	freopen("out.out", "w", stdout);
#endif
	
	get(V)
	F(u, 1, V)
	{
		get(v)
		edd(u, v);
		edd(v, u);
	}

	de[ROOT] = 0, fa[ROOT] = ROOT, sz[ROOT] = 1;
	dfs1(ROOT);

	dnt = 0, top[ROOT] = ROOT, id[ROOT] = ++dnt, a[dnt] = a0[ROOT];
	dfs2(ROOT);

	st.build(a, 1, dnt);
	
	qjj
	{
		get(op, u)
		if (op)	// install
		{
			UPRT((de[u] - de[ROOT] + 1) - SP::sum(u, ROOT)), PC(10);
			SP::set(u, ROOT, 1);
		}
		else	// uninstall
		{
			UPRT(SP::sum(u)), PC(10);
			SP::set(u, 0);
		}
	}
	
	return 0;
}




继续加油吧~ (期待认识即将一起奋斗的小伙伴们hhh)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值