线段树维护势能类 / 均摊类问题:CF403E

https://www.luogu.com.cn/problem/CF403E

场上想对于一棵树的某个子树把所有向外边全部删掉

变成dfn序一个在子树区间,一个不在的问题


易发现这个问题可以用线段树维护

在一个点在其dfn序加入另一个点,维护区间另一个点dfn序的最大和最小值

如果不在询问区间里,直接递归

易证明均摊是 O ( n log ⁡ n ) O(n\log n) O(nlogn)

对于这类操作均摊的问题,都可以考虑用线段树维护

//5.1k
#include<bits/stdc++.h>
using namespace std;
//#define int long long
inline int read(){int x=0,f=1;char ch=getchar(); while(ch<'0'||
ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return x*f;}
#define Z(x) (x)*(x)
#define pb push_back
//mt19937 rand(time(0));
//mt19937_64 rand(time(0));
//srand(time(0));
#define N 200010
//#define M
//#define mo
#define fi first
#define se second
int n, m, i, j, k, idx;

//map<pair<int, int>, int>mp[2]; 
// unordered_map<pair<int, int>, int>mp; 
int mp[2][N]; 
queue<pair<int, int> >q[2]; 

struct Segment_tree {
	int o; 
	int tot, ls[N<<2], rs[N<<2]; 
	int dfn[N<<2], low[N<<2];  
	int mn[N<<2], mx[N<<2]; 
	struct node {
		int x, id;  
	};
	vector<node>v[N<<2]; 
	int ST[N<<2], ED[N<<2]; 
	void build(int &k, int l, int r) {
		if(!k) k=++tot, mn[k]=l, mx[k]=r; 
		if(l==r) return ; 
		int mid=(l+r)>>1; 
		build(ls[k], l, mid); 
		build(rs[k], mid+1, r); 
	}
	void add(int k, int l, int r, int x, int y, int z) {//序x 点y 
		if(l==r) return v[k].pb({y, z}), void(); 
		int mid=(l+r)>>1; 
		if(x<=mid) add(ls[k], l, mid, x, y, z); 
		else add(rs[k], mid+1, r, x, y, z); 
	}
	void push_up(int k) {
		mn[k]=min(mn[ls[k]], mn[rs[k]]); 
		mx[k]=max(mx[ls[k]], mx[rs[k]]); 
	}
	void remke(int k, int l, int r) {
		if(l==r) {
			int &st = ST[k], &ed = ED[k]; 
			sort(v[k].begin(), v[k].end(), [this] (node x, node y) 
				 {return dfn[x.x]<dfn[y.x]; } ); //v里面存点 
			st=0; ed=v[k].size()-1; 
			if(ed>=0) mn[k]=dfn[v[k][st].x], mx[k]=dfn[v[k][ed].x]; 
//			printf("# %d [%d -> %d] %d %d\n", o, l, low[l], mn[k], mx[k]); 
			return ; 
		}
		int mid=(l+r)>>1; 
		remke(ls[k], l, mid); 
		remke(rs[k], mid+1, r); 
		push_up(k); 
	}
	void tak(int k, int l, int r, int x, int y) {
//		printf("%d [%d ]")
		if(l>=x && r<=y) {
			if(l==r) {
				if(!v[k].size()) return mn[k]=mx[k]=l, void(); 
				int &st = ST[k], &ed = ED[k]; 
				while(st<=ed && dfn[v[k][st].x]<x) {
					int u = v[k][st].x, id = v[k][st].id, v = low[l]; 
					if(!mp[o^1][id]) {
//						printf("# %d : (%d %d) | %d\n", o^1, u, v, id); 
//						mp[o^1][{u, v}]=mp[o^1][{v, u}]=1; 
						mp[o^1][id]=1; 
						q[o^1].push({u, v}); 
					}
					++st; 
				}
				while(st<=ed && dfn[v[k][ed].x]>y) {
					int u = v[k][ed].x, id = v[k][ed].id, v = low[l]; 
					if(!mp[o^1][id]) {
//						printf("# %d : (%d %d) | %d\n", o^1, u, v, id); 
						mp[o^1][id]=1; 
						q[o^1].push({u, v}); 
					} 
					--ed; 
				}
				if(st<=ed) mn[k]=dfn[v[k][st].x], mx[k]=dfn[v[k][ed].x]; 
				else mn[k]=mx[k]=l; 
				return; 
			}
			int mid=(l+r)>>1; 
			if(mn[ls[k]]<x || mx[ls[k]]>y) tak(ls[k], l, mid, x, y); 
			if(mn[rs[k]]<x || mx[rs[k]]>y) tak(rs[k], mid+1, r, x, y); 
			push_up(k); 
			return ; 
		}
		int mid=(l+r)>>1; 
		if(x<=mid) tak(ls[k], l, mid, x, y); 
		if(y>=mid+1) tak(rs[k], mid+1, r, x, y); 
		push_up(k); 
	}
}Seg[2]; //dfn序是哪棵树,这里就对应哪棵树 

struct Tree {
	int o; 
	int dfn[N], low[N], st[N], ed[N]; 
	int i, j, k, tot, rt; 
	vector<pair<int, int> >G[N];
	vector<pair<int, int> >ve; 
	map<pair<int, int>, int>id; 
	
	void dfs(int x, int fa) {
		dfn[x]=++tot; st[x]=tot; low[tot]=x; 
		for(auto t : G[x]) if(t.first!=fa) 
			dfs(t.first, x); 
		ed[x]= tot; 
	}
	void Read() {
		for(i=2; i<=n; ++i) {
			k=read(); G[i].pb({k, i-1}); 
			G[k].pb({i, i-1}); 
			ve.pb({i, k}); 
			id[{i, k}]=id[{k, i}]=i-1; 
		}
		Seg[o].build(rt, 1, n); 
		dfs(1, 0); 
//		for(i=1; i<=n; ++i) printf("%d ", dfn[i]); puts(""); 
	}
	void pre();
	void tak(int u, int v) { // 取走其中一个点 [l, r] 内的 
//		printf("[%d %d]\n", max(st[u], st[v]), min(ed[u], ed[v])); 
		Seg[o].tak(1, 1, n, max(st[u], st[v]), min(ed[u], ed[v])); 
	}
};

Tree T[2];

void Tree::pre() {
//	printf("# %d\n", o); 
	for(auto t : ve) {
		int x=t.fi, y=t.se; 
		int u=T[o^1].dfn[x], v=T[o^1].dfn[y]; 
//		printf("(%d %d) [%d %d] %d\n", x, y, u, v, x-1); 
		Seg[o^1].add(1, 1, n, u, y, x-1); 
		Seg[o^1].add(1, 1, n, v, x, x-1); 
	}
	Seg[o^1].remke(1, 1, n); 
}

signed main()
{
//	freopen("in.txt", "r", stdin);
//	freopen("out.txt", "w", stdout);
//	T=read();
//	while(T--) {
//
//	}
	n=read(); 
	T[0].o=0; T[1].o=1; 
	Seg[0].o=0; Seg[1].o=1; 
	T[0].Read(); T[1].Read(); 
	memcpy(Seg[0].dfn, T[0].dfn, sizeof(T[0].dfn)); 
	memcpy(Seg[0].low, T[0].low, sizeof(T[0].low)); 
	memcpy(Seg[1].dfn, T[1].dfn, sizeof(T[1].dfn)); 
	memcpy(Seg[1].low, T[1].low, sizeof(T[1].low)); 
	T[0].pre(); T[1].pre(); 
	idx=read(); 
	for(auto t : T[0].id) 
		if(t.se==idx && !mp[0][idx]) {
			int u = t.fi.fi, v = t.fi.se; 
			q[0].push({u, v}); 
			mp[0][idx]=1; 
//			printf("(%d %d) %d\n", u, v, ); 
		}
		
	k=0; 
	vector<int>ans; 
	while(!q[k].empty()) {
		printf(k ? "Red\n" : "Blue\n"); 
		ans.clear(); 
		while(!q[k].empty()) {//q里放的是点 
			auto t = q[k].front(); q[k].pop(); 
			ans.pb(T[k].id[{t.fi, t.se}]); 
			T[k].tak(t.fi, t.se); 
		}
		sort(ans.begin(), ans.end()); 
		for(auto i : ans) printf("%d ", i); 
		puts("");  
		k^=1; 
	}
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对算法有兴趣的可以来看看 在自然数,且所有的数不大于30000的范围内讨论一个问题:现在已知n条线段,把端点依次输入告诉你,然后有m个询问,每个询问输入一个点,要求这个点在多少条线段上出现过; 最基本的解法当然就是读一个点,就把所有线段比一下,看看在不在线段中; 每次询问都要把n条线段查一次,那么m次询问,就要运算m*n次,复杂度就是O(m*n) 这道题m和n都是30000,那么计算量达到了10^9;而计算机1秒的计算量大约是10^8的数量级,所以这种方法无论怎么优化都是超时 因为n条线段是固定的,所以某种程度上说每次都把n条线段查一遍有大量的重复和浪费; 线段就是可以解决这类问题的数据结构 举例说明:已知线段[2,5] [4,6] [0,7];求点2,4,7分别出现了多少次 在[0,7]区间上建立一棵满二叉:(为了和已知线段区别,用【】表示线段中的线段) 【0,7】 / \ 【0,3】 【4,7】 / \ / \ 【0,1】 【2,3】 【4,5】 【6,7】 / \ / \ / \ / \ 【0,0】 【1,1】 【2,2】 【3,3】 【4,4】 【5,5】 【6,6】 【7,7】 每个节点用结构体: struct line { int left,right; // 左端点、右端点 int n; // 记录这条线段出现了多少次,默认为0 }a[16]; 和堆类似,满二叉的性质决定a[i]的左儿子是a[2*i]、右儿子是a[2*i+1]; 然后对于已知的线段依次进行插入操作: 从根开始调用递归函数insert // 要插入的线段的左端点和右端点、以及当前线段中的某条线段 void insert(int s,int t,int step)

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值