P4097 [HEOI2013] Segment 题解 || 【学习笔记】李超线段树

题目传送门

众所周知,线段树是维护区间的,但是这里的线段不能直接用区间维护,所以我们首先转换一下题意:(这部分是我学了 OI-wiki 上的)

  • 加入一个一次函数,定义域为 \([l,r]\)
  • 给定 \(k\),求定义域包含 \(k\) 的所有一次函数中,在 \(x = k\) 处取值最大的那个,如果有多个函数取值相同,选编号最小的。

在这里解释一下 OI-wiki 中讲的为什么当一个线段不存在斜率时可以转换成斜率为 \(0\),截距为 \(\max(y_0,y_1)\) 的直线。

其实也比较显然,该线段所有点会且仅会与 \(x = x_0\) 相交,因此我们只用取纵坐标最大的点(纵坐标即 \(\max(y_0,y_1)\))作为交点,其余的点不会使该线段更优,因此可以忽略。

这样,我们就将其转化为一个区间修改,单点查询的问题。

依照传统线段树的思路,我们还是通过分治和懒标记进行区间修改。

但是在一个区间内是没有最优线段的,只有在取一个特定的 \(x\)才有。所以,我们考虑每个节点维护包含对应区间所有直线在 横坐标 \(x\) 取一个一个特殊点时的最优线段。

不难想到这个特殊点为该区间的中点。

所以每个结点的懒标记维护的是横坐标 \(x\) 取该区间中点时的最优线段(但是这个懒标记无法保证任何时候都是在中点处最优的线段,原因讲到查询时会解释)。

在传统线段树中,区间修改(带懒标记应该是这样的):

//[l,r]表示当前区间,[L,R]表示修改的区间
void update(int root,int l,int r,int L,int R,int id){
	if(out_range(l,r,L,R))//完全不在[L,R]内直接返回
		return;
	if(in_range(l,r,L,R)){//完全包含在[L,R]内更新懒标记
		make_tag(root,l,r,id);
		return;
	}
    //分别进入两子区间
	update(lchild,l,mid,L,R,id);
	update(rchild,mid + 1,r,L,R,id);
}

但是我们发现,在本题中,由懒标记的定义,当该线段的一部分(\([l,r]\))完全包含在修改区间 \([L,R]\) 中时,不仅维护 \([l,r]\) 区间结点的懒标记会发生变化,其子节点也会发生变化,所以我们还得把懒标记下传。

对于该区间,如果在中点处取值当前修改线段与该结点维护的最优线段更优,直接拿当前线段替换,并把当前线段替换成在中点处不优的线段。

然后,我们分别比较两线段在左端点 \(l\) 的取值和在右端点 \(r\) 的取值:

如果中点处不优的线段在左端点处的取值更优,那么递归到左子节点下传。

如果中点处不优的线段在右端点处的取值更优,那么递归到右子节点下传。

如果都不优,那么该线段在该区间及子区间将来无论如何都不可能最优线段,可以停止下传。

int CMP(double x,double y){//比较两浮点数 
	if(x - y > eps)
		return 1;
	if(y - x > eps)
		return -1;
	return 0;
}
bool cmp(int id1,int id2,int x){//比较线段id1在x处是否优于id2 ,用于提升代码可读性和降低编码复杂度
	int comp = CMP(calc(id1,x),calc(id2,x));
	return comp == 1 || (!comp && id1 < id2);//id1比id2在x处更优,当且仅当id1在x处的取值更优或x取值相等但id1编号更小
}
void push_down(int root,int l,int r,int id){
	if(cmp(id,t[root].id,mid))//如果当前线段比结点维护的线段更优,替换掉
		swap(id,t[root].id);
        //将不是更优的线段下传(因为此时 id 中点处不优,所以以下最多只有1个if成立)
	if(cmp(id,t[root].id,l))
		push_down(lchild,l,mid,id);
	if(cmp(id,t[root].id,r))
		push_down(rchild,mid + 1,r,id);
}

因为 push_down 函数两个 if 最多只有一个成立,所以 push_down 函数的复杂度为 \(O(\log V)\)\(V\)\(x\) 最大值)。同样的,update函数复杂度也是 \(O(\log V)\)(覆盖区间个数为 \(O(\log V)\)),每个覆盖区间又要一次 \(O(\log V)\)push_down,所以修改部分的总复杂度为 \(O(\log^2 V)\)

我们看到,查询部分显然是个单点查询问题,但真的只需要访问代表查询点的那个叶节点吗?

不是的。

上文提到,懒标记无法保证任何时候都是在中点处最优的线段,很容易构造出(去除强制在线的影响):

1 7 2 9 2
1 2 2 10 10

我们令整个区间长度为 \(10\)

在本例中,加入第 \(1\) 条使其在 \(x = 8\) 处最优。但是第二条线段在 \(x = 8\) 处显然更优,但由于第 \(1\) 条线段与与整个区间的中点无交,所以第 \(2\) 条线段不会继续下传,因此维护 \([6,10]\) 区间节点(中点为 \(8\))维护的“最优”还是线段 \(1\)

所以,我们需要将所有包含 \(x = k\) 的区间对应的结点全部扫一遍,取所有结点懒标记的最优者。

在这里维护了一个 better 函数,因此这里的 query 返回值不需要用 pair

int better(int id1,int id2,int x){//返回id1,id2在x处较优者 
	return cmp(id1,id2,x) ? id1 : id2;
}
int query(int root,int l,int r,int k){
	if(out_range(k,k,l,r))
		return 0;
	if(l == r)//递归边界
		return t[root].id;
	return better(t[root].id,better(query(lchild,l,mid,k),query(rchild,mid + 1,r,k),k),k);
}

复杂度为 \(O(\log V)\)

AC code:

#include<iostream>
#define lchild (root << 1)
#define rchild ((root << 1) + 1)
#define mid ((l + r) >> 1)
using namespace std;
const int N = 1e5 + 9,MAXX = 39989,MAXY = 1e9;
const double eps = 1e-9;
struct line{//存线段的结构体
	double k,b;
} a[N];
int cnt;
void add_line(int x0,int y0,int x1,int y1){//加入一线段
	cnt++;
	if(x0 == x1){
		a[cnt].k = 0;
		a[cnt].b = max(y0,y1);
		return;
	}
	a[cnt].k = (double)(y1 - y0) / (double)(x1 - x0);
	a[cnt].b = (double)y0 - (double)(x0 * a[cnt].k);
}
double calc(int id,double x){//计算第i条直线在x处的值。 
	return a[id].k * x + a[id].b;
}
//1:x > y
//-1:x < y
//0:x = y
int CMP(double x,double y){//比较两浮点数 
	if(x - y > eps)
		return 1;
	if(y - x > eps)
		return -1;
	return 0;
}
bool cmp(int id1,int id2,int x){//比较线段id1在x处是否优于id2 ,用于提升代码可读性和降低编码复杂度
	int comp = CMP(calc(id1,x),calc(id2,x));
	return comp == 1 || (!comp && id1 < id2);
}
int better(int id1,int id2,int x){//返回id1,id2在x处较优者 
	return cmp(id1,id2,x) ? id1 : id2;
}
bool in_range(int l,int r,int L,int R){
	return L <= l && r <= R;
}
bool out_range(int l,int r,int L,int R){
	return l > R || r < L;
}
struct node{
	int id;//该节点维护区间最优线段编号 
} t[MAXX << 2];
void push_down(int root,int l,int r,int id){
	if(cmp(id,t[root].id,mid))//如果当前线段比结点维护的线段更优,替换掉
		swap(id,t[root].id);
        //将不是更优的线段下传(因为此时 id 中点处不优,所以以下最多只有1个if成立)
	if(cmp(id,t[root].id,l))
		push_down(lchild,l,mid,id);
	if(cmp(id,t[root].id,r))
		push_down(rchild,mid + 1,r,id);
}
void update(int root,int l,int r,int L,int R,int id){
	if(out_range(l,r,L,R))
		return;
	if(in_range(l,r,L,R)){
		push_down(root,l,r,id);
		return;
	}
	update(lchild,l,mid,L,R,id);
	update(rchild,mid + 1,r,L,R,id);
}
int query(int root,int l,int r,int k){
	if(out_range(k,k,l,r))
		return 0;
	if(l == r)
		return t[root].id;
	return better(t[root].id,better(query(lchild,l,mid,k),query(rchild,mid + 1,r,k),k),k);
}
int n,ans;
int main(){//主函数就不需要我解释了吧
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin >> n;
	for(int i = 1;i <= n;i++){
		int op;
		cin >> op;
		if(op == 0){
			int k;
			cin >> k;
			k = (k + ans - 1) % MAXX + 1;
			ans = query(1,1,MAXX,k);
			cout << ans << '\n';
		}
		if(op == 1){
			int x0,y0,x1,y1;
			cin >> x0 >> y0 >> x1 >> y1;
			x0 = (x0 + ans - 1) % MAXX + 1;
			x1 = (x1 + ans - 1) % MAXX + 1;
			y0 = (y0 + ans - 1) % MAXY + 1;
			y1 = (y1 + ans - 1) % MAXY + 1;
			if(x0 > x1){
				swap(x0,x1);
				swap(y0,y1);
			}
			//cout << x0 << ' ' << y0 << ' ' << x1 << ' ' << y1 << '\n';
			add_line(x0,y0,x1,y1);
			update(1,1,MAXX,x0,x1,cnt);
		}
	}
	return 0;
}
posted @ 2024-02-04 11:21  5t0_0r2  阅读(18)  评论(0编辑  收藏  举报