[扫描线] 数据结构测试(2025.3.22)

哈哈哈暴力大赛,赛时暴力打满(差40pts打满。我太菜了暴力都不会呜呜)喜提80pts,(T1没想到暴力。

难度:T2<T1<T3.

 

T1


第1题     团队 查看测评数据信息

有n个工人,第i个工人的能力是v[i], 他只与能力在L[i]到R[i]之间的人在一起工作,问最多能选出多少人在一起工作。

 

输入格式

 

第一行,一个整数n,  1 <= n <= 100000。

接下来有n行,第i行是: L[i]、v[i]、R[i], 其中1<= L[i] <= v[i] <= R[i] <= 300000。

 

【提示】

有40%数据n<=100。

 

输出格式

 

一个整数。

 

输入/输出例子1

输入:

4

2 8 9

1 4 7

3 6 8

5 8 10

 

 

输出:

3

 

 

 

输入/输出例子2

输入:

6

3 5 16

1 6 11

4 8 12

7 9 16

2 10 14

8 13 15

 

 

输出:

4

 

 

 

样例解释

 

样列一解释: 可以选择第1,第3,第4这三人。

样列二解释: 可以选择第1,第2,第3,第5这四人。

 

先看40%的分:

首先得想到,最终选了一些人,所组成的区间一定是一个交集 [L, R]。

这点我没想到,导致整题拜拜。如果正面想,想如何去求答案这个过程很难,不妨考虑从答案结果出发,然后根据结果直接反推,看哪些过程符合条件,就避免了过程的枚举。因为答案确定了过程也确定了。

然后就容易了,枚举L, 枚举R,再枚举n个人。看看有几个人满足在[L, R]内。其中L,R只会是每个人的区间端点值。

O(n^3)

 

100%的分:提供两种方法。

方法一


L,R看成x,y轴上的一个数。这样[L, R]变成一个坐标轴上的点(L, R)。区间->坐标轴上的点

转化:区间有哪些人->考虑人对区间的贡献。我们就看什么时候人会对区间造成贡献,或者说人对哪些区间有贡献。

此时这个区间代表选了一些人所组成的区间。

 

假设 人:[L, v, R](是已知的)。某个区间:[L2, R2](是未知的)
只要 L<=L2<=v && v<=R2<=R,即人完全包含区间,就是人对区间有贡献。

可以根据题意理解:因为你只和能力值在你这个区间范围内的人工作,所以你一定要包含 [L, R] 这个区间。

其次,[L, R]是选了某些人后组成区间,这些人只和他们的能力范围并集内的人合作,所以 v 一定在 [L, R] 内。

 

L,R,L2,R2,v,再次转回坐标轴就是一个矩形。矩形内每个点都是原来的区间。

即:x 轴上有 L,v;y 轴上有 v, R。

转化思想:一个区间可以转成坐标轴上的点,多个区间合并在一起取值(形如 x<=L<=y<=R<=z)可以转换成坐标轴上的矩形。

 

想要找一个区间[L, R]内,工作的人最多,就转化成:找一个点,使得这个点被矩形覆盖的次数最多->扫描线(其实就是扫描线里,线段树存一个最大值)。

考虑问题的次序思想:不同顺序考虑问题难易程度将不一样,如此题是先考虑区间再考虑人会很难,先考虑人再考虑区间是简单的

 

一些细节:

1.不同于平常的扫描线,这里线段树叶结点存的是一个点,而非线段。

2.注意排序的时候,加法操作要放前面,不然最大值计算会少算。

#include <bits/stdc++.h>
using namespace std;
const int N=300005;

int n; 
long long ans=0;
struct node
{
	int L, v, R;
}b[N];
struct Tree
{
	int Max, add;
}tr[N<<4];
struct smx
{
	int x, ay, by, add;
};
vector<smx> a;
vector<int> p;
bool cmp(smx a, smx b)
{
	if (a.x!=b.x) return a.x<b.x;
	return a.add>b.add;
}
void push_up(int id, int L, int R)
{
	tr[id].Max=max(tr[id*2].Max, tr[id*2+1].Max);
}
void push_down(int id)
{
	tr[id*2].add+=tr[id].add;
	tr[id*2+1].add+=tr[id].add;
	
	tr[id*2].Max+=tr[id].add;
	tr[id*2+1].Max+=tr[id].add;
	
	tr[id].add=0;
}
void add(int id, int L, int R, int qL, int qR, int k)
{
	if (qL<=p[L] && p[R]<=qR)
	{
		tr[id].Max+=k;
		tr[id].add+=k;
		
		return ;
	}
//	if (L+1==R) return ;
	push_down(id);
	
	int mid=L+R>>1;
	
	if (qL<=p[mid]) add(id*2, L, mid, qL, qR, k);
	if (qR>=p[mid+1]) add(id*2+1, mid+1, R, qL, qR, k);
	
	push_up(id, L, R);
}
int main()
{
	scanf("%d", &n);
	for (int i=1; i<=n; i++) scanf("%d%d%d", &b[i].L, &b[i].v, &b[i].R);
	
	for (int i=1; i<=n; i++)
	{
		int ax=b[i].L, ay=b[i].v, bx=b[i].v, by=b[i].R;
		a.push_back({ax, ay, by, 1});
		a.push_back({bx, ay, by, -1});
		p.push_back(ay), p.push_back(by);
	}
	sort(a.begin(), a.end(), cmp);
	
	sort(p.begin(), p.end());
	p.erase(unique(p.begin(), p.end()), p.end());
	
	for (int i=0; i<a.size(); i++)
	{
		if (i>0) ans=max(ans, 1ll*tr[1].Max);
		add(1, 0, p.size()-1, a[i].ay, a[i].by, a[i].add);
	}
	
	printf("%lld", ans);
	return 0;
}
/*
4
2 8 9
1 4 7
3 6 8
5 8 10
*/

  

 

方法二


相比方法一少了套路,多了思考。

 

首先得想到,最终选了一些人的区间一定是一个交集 [L, R]

对于一类区间问题,可以考虑枚举 L,维护 R。

 

假设有人[L2, v, R2] 。即满足:L2<v<R2

先分析一下L。如果L2>L,肯定不会在这个区间,这样一定有L=L2,因为是交集嘛。
所以把人按 L2 从小到大排序。每个L2一定满足, L2<=L

 

考虑人的贡献。也就是考虑R的答案,怎么取值R才可能让 [L2, v, R2] 对 [L, R] 有贡献?
v<=R<=R2 就可以贡献了。分析同 L,因为是交集。

 

这里用线段树维护 R,L=L2时,[v, R2]+1。然后怎么知道对右端点有贡献有多少人?查询[v, R2] Max。

当L>v时,[v, R2]-1.

 

此处无代码。但是可以看看:[题解]CF377D Developing Game - 洛谷专栏

 

 

 

T2


第2题     好区间 查看测评数据信息

有n个整数从左往右排成一行,构成序列A[1...n]。

对于1 <= i <= j <= n, 子序列A[i...j]被称为“优质子序列”当且仅当:存在某个整数x,使得x在子序列A[i...j]中恰好出现过一次。

问序列A总共有多少个不同的子序列。

 

输入格式

 

第一行,一个整数n,  其中2 <= n <= 200000。

第二行,n个整数,第i个整数是A[i], 1 <= A[i] <= n。

 

【提示】

有40%的数据,  n <= 1000。

 

输出格式

 

一个整数。

 

输入/输出例子1

输入:

10

1 2 1 4 3 3 3 2 2 4

 

 

输出:

47

 

输入/输出例子2

输入:

5

2 2 1 2 1

 

 

输出:

12

 

 

样例解释

 

样例二解释:

image.png

 

40%:枚举L,R,判断是否满足条件。


100%:
暴力发现不好操作。次序原因,因为先想区间再判区间里的特殊数比较麻烦。反过来想。先想特殊数(即区间内出现一次的数),再想区间。

考虑每个特殊数对区间的贡献。

一个trict:如果有“只出现一次的数“,都可以考虑记录:pre[a[i]], nxt[a[i]]。分别表示a[i]前面最先出现的数,和后面最先出现的数。

假设被贡献的区间是[L, R]
满足 pre[a[i]]+1<=L<=pos[a[i]]  && pos[a[i]]<=R<=lst[a[i]]+1.

又是很多区间。放回二维坐标系。就是一个矩形。

 

然后转换成矩形面积并集。

#include <bits/stdc++.h>
using namespace std;
const int N=200005;

int n, b[N], t[N], nxt[N], pre[N];
long long ans=0;

struct Tree
{
	int cover, len;
}tr[N<<4];
struct node
{
	int x, ay, by, add;
};
vector<node> a;
vector<int> p;
bool cmp(node a, node b)
{
	return a.x<b.x;
}
void push_up(int id, int L, int R)
{
	if (tr[id].cover>=1) tr[id].len=p[R]-p[L];
	else tr[id].len=tr[id*2].len+tr[id*2+1].len;
}
void add(int id, int L, int R, int qL, int qR, int k)
{
	if (qL<=p[L] && p[R]<=qR)
	{
		tr[id].cover+=k;
		push_up(id, L, R);
		
		return ;
	}
	if (L+1==R) return ;
	
	int mid=L+R>>1;
	
	if (qL<=p[mid]) add(id*2, L, mid, qL, qR, k);
	if (qR>=p[mid]) add(id*2+1, mid, R, qL, qR, k);
	
	push_up(id, L, R);
}
int main()
{
	scanf("%d", &n);
	for (int i=1; i<=n; i++) scanf("%d", &b[i]);
	for (int i=1; i<=n; i++)
	{
		if (t[b[i]]) nxt[t[b[i]]]=i;
		pre[i]=t[b[i]];
		
		nxt[i]=n+1, t[b[i]]=i;
	}
	
	for (int i=1; i<=n; i++)
	{
		int ax=pre[i], ay=i, bx=i, by=nxt[i];
		a.push_back({ax, ay, by, 1});
		a.push_back({bx, ay, by, -1});
		p.push_back(ay), p.push_back(by);
	}
	sort(a.begin(), a.end(), cmp);
	
	sort(p.begin(), p.end());
	p.erase(unique(p.begin(), p.end()), p.end());
	
	for (int i=0; i<a.size(); i++)
	{
		if (i>0) ans+=1ll*tr[1].len*(a[i].x-a[i-1].x);
		add(1, 0, p.size()-1, a[i].ay, a[i].by, a[i].add);
	}
	
	printf("%lld", ans);
	return 0;
}

  

 

 

T3


第3题     连续子区间 查看测评数据信息

有一个1至n的排列P ,如果排列P中的第L个至第R个数是连续的,那么我们称[L,R]是好区间。例如,[1, 3, 2, 5, 4]中的好区间有:

[1,1], [1, 3], [1, 5], [2, 2], [2, 3], [2, 5], [3, 3], [4, 4], [4, 5], [5, 5]。

有q次询问,每次问[L,R]内,有多少子区间是好的?

 

输入格式

 

第一行,一个整数n , 1 <= n <= 120000。

第二行,n个整数,第i个整数表示P[i],  所有 1 <= P[i] <= n, 且P[i]互异。

第三行, 一个整数q表示询问的次数, 1 <= q <= 120000。

接下来有q行,每行两个整数:L和R,表示询问区间[L,R]有多少个子区间是好的。

 

【提示】

有40%数据,1 <= n,q <= 1000。

 

输出格式

 

共q行,每行一个整数。

 

输入/输出例子1

输入:

5

1 3 2 5 4

15

1 1

1 2

1 3

1 4

1 5

2 2

2 3

2 4

2 5

3 3

3 4

3 5

4 4

4 5

5 5

 

 

输出:

1

2

5

6

10

1

3

4

7

1

2

4

1

3

1

 

 

样例解释

 

搬运一下题解,写的还挺好:

 

posted @ 2025-03-22 19:42  cn是大帅哥886  阅读(10)  评论(0)    收藏  举报