线段树/扫描线问卷调查反馈——Rmq Problem / mex(主席树),Boring Queries(二分+st表+主席树),Colorful Squares(扫描线)

Rmq Problem / mex

luogu4137

a i a_i ai建权值线段树

再加上可持久化

对于第 R R R个版本的线段树,每个叶子 x x x存的是 a i = x a_i=x ai=x的所有 i i i中最大的小于 R R R的位置 i i i

那么询问 [ L , R ] [L,R] [L,R]就转化成 R R R版本线段树中所有小于 L L L的叶子中的最小值

#include <cstdio>
#include <iostream>
using namespace std;
#define maxn 200005
int n, Q, cnt;
int a[maxn], rt[maxn];
struct node { int l, r, Min; }t[maxn * 50];

void modify( int lst, int &now, int l, int r, int val, int pos ) {
	if( ! now ) now = ++ cnt;
	if( l == r ) { t[now].Min = pos; return; }
	int mid = ( l + r ) >> 1;
	if( val <= mid ) {
		t[now].r = t[lst].r;
		modify( t[lst].l, t[now].l, l, mid, val, pos );
	}
	else {
		t[now].l = t[lst].l;
		modify( t[lst].r, t[now].r, mid + 1, r, val, pos );
	}
	t[now].Min = min( t[t[now].l].Min, t[t[now].r].Min );
}

int query( int now, int l, int r, int ti ) {
	if( l == r ) return l;
	int mid = ( l + r ) >> 1;
	if( t[t[now].l].Min <= ti ) return query( t[now].l, l, mid, ti );
	else return query( t[now].r, mid + 1, r, ti );
}

int main() {
	scanf( "%d %d", &n, &Q );
	for( int i = 1;i <= n;i ++ ) scanf( "%d", &a[i] );
	for( int i = 1;i <= n;i ++ ) {
		a[i] = min( a[i], n );
		modify( rt[i - 1], rt[i], 0, n, a[i], i );
	}
	for( int i = 1, l, r;i <= Q;i ++ ) {
		scanf( "%d %d", &l, &r );
		printf( "%d\n", query( rt[r], 0, n, l - 1 ) );
	}
	return 0;
}

Boring Queries

CF1422F

a i ∈ [ 1 , 2 e 5 ] → a_i\in[1,2e5]\rightarrow ai[1,2e5] a i a_i ai分解质因子后,最多只会有一个 > 2 e 5 ≈ 450 >\sqrt{2e5}≈450 >2e5 450的质因数

而在450内的质因数有87

所以按质因数大小分块

预处理 a i a_i ai含有的小于450的质因数 p r i m e j \rm prime_j primej的个数

l c m \rm lcm lcm可以表示为 ∏ max ⁡ l r p i \prod\max_l^rp_i maxlrpi(每一个质因数的个数最大值的乘积)

对于每个质因数开一个 s t st st表,开 87 87 87个就行,预处理出该质因数在 a [ l , r ] a[l,r] a[l,r]内出现次数的最大值

剩下的一个 > 450 >450 >450的质因数,利用线段树来维护区间 [ l , r ] [l,r] [l,r]内所有这些大质因数乘积

  • 如果没有就往里面扔 1 1 1
  • 如果有
    • 考虑对于区间 [ l , r ] [l,r] [l,r]内多次出现的 x x x只能统计一次
    • p r e i : a i pre_i:a_i prei:ai上一次出现的位置
    • 只在区间 [ l , r ] [l,r] [l,r] x x x第一次出现的时候把 x x x统计进答案
    • 这些位置的特点是 p r e i ≤ l − 1 pre_i\le l-1 preil1
    • 所以相当于 ∀ l ≤ i ≤ r ∏ i [ p r e i ≤ l − 1 ] a i \forall_{l\le i\le r} \prod_i\Big[pre_i\le l-1\Big]a_i liri[preil1]ai
    • 主席树维护, r t [ r ] rt[r] rt[r]的叶子维护该值距离 r r r最近的位置
    • 当时候直接查 r t [ l − 1 ] rt[l-1] rt[l1]就行

跟第一题很类似的主席树维护

#include <cmath>
#include <cstdio>
#include <iostream>
using namespace std;
#define mod 1000000007
#define maxn 200005
struct node { int l, r, val; }t[maxn * 20];
int n, Q, cnt, tot;
bool vis[455];
int prime[90], a[maxn], rt[maxn], nxt[maxn], pos[maxn];
int mi[90][20];
char st[90][maxn][20];

void sieve() {
	for( int i = 2;i <= 450;i ++ ) {
		if( ! vis[i] ) prime[++ cnt] = i;
		for( int j = 1;j <= cnt and i * prime[j] <= 450;j ++ ) {
			vis[i * prime[j]] = 1;
			if( i % prime[j] == 0 ) break;
		}
	}
}

void build() {
	for( int k = 1;k <= cnt;k ++ )
		for( int j = 1;j < 20;j ++ )
			for( int i = 1;i + ( 1 << j ) - 1 <= n;i ++ )
				st[k][i][j] = max( st[k][i][j - 1], st[k][i + ( 1 << j - 1 )][j - 1] );
}

int query( int k, int l, int r ) {
	int i = log2( r - l + 1 );
	return max( st[k][l][i], st[k][r - ( 1 << i ) + 1][i] );
}

void build( int &now, int l, int r ) {
	if( ! now ) now = ++ tot;
	t[now].val = 1;
	if( l == r ) return;
	int mid = l + r >> 1;
	build( t[now].l, l, mid );
	build( t[now].r, mid + 1, r );
}

void modify( int now, int l, int r, int pos, int val ) {
	if( l == r ) { t[now].val = val; return; }
	int mid = l + r >> 1;
	if( pos <= mid ) modify( t[now].l, l, mid, pos, val );
	else modify( t[now].r, mid + 1, r, pos, val );
	t[now].val = 1ll * t[t[now].l].val * t[t[now].r].val % mod;
}

void modify( int lst, int &now, int l, int r, int pos, int val ) {
	if( ! now ) now = ++ tot;
	if( l == r ) { t[now].val = val; return; }
	int mid = l + r >> 1;
	if( pos <= mid ) {
		t[now].r = t[lst].r;
		modify( t[lst].l, t[now].l, l, mid, pos, val );
	}
	else {
		t[now].l = t[lst].l;
		modify( t[lst].r, t[now].r, mid + 1, r, pos, val );
	}
	t[now].val = 1ll * t[t[now].l].val * t[t[now].r].val % mod;
}

int query( int now, int l, int r, int L, int R ) {
	if( ! now or r < L or R < l ) return 1;
	if( L <= l and r <= R ) return t[now].val;
	int mid = ( l + r ) >> 1;
	return 1ll * query( t[now].l, l, mid, L, R ) * query( t[now].r, mid + 1, r, L, R ) % mod;
}

int main() {
	sieve();
	scanf( "%d", &n );
	for( int i = 1;i <= n;i ++ ) {
		scanf( "%d", &a[i] );
		for( int j = 1;j <= cnt;j ++ )
			if( a[i] % prime[j] == 0 )
				while( a[i] % prime[j] == 0 )
					st[j][i][0] ++, a[i] /= prime[j];
	}
	build();
	build( rt[0], 1, n );
	for( int i = 1;i <= n;i ++ ) {
		if( ! pos[a[i]] ) modify( rt[0], 1, n, i, a[i] );
		else nxt[pos[a[i]]] = i;
		pos[a[i]] = i;
	}
	for( int i = 1;i <= n;i ++ )
		if( nxt[i] ) modify( rt[i - 1], rt[i], 1, n, nxt[i], a[nxt[i]] );
		else rt[i] = rt[i - 1];
	for( int i = 1;i <= cnt;i ++ ) {
		mi[i][0] = 1;
		for( int j = 1;j < 20;j ++ )
			mi[i][j] = 1ll * mi[i][j - 1] * prime[i] % mod;
	}
	scanf( "%d", &Q );
	int lst = 0, l, r;
	while( Q -- ) {
		scanf( "%d %d", &l, &r );
		l = ( l + lst ) % n + 1;
		r = ( r + lst ) % n + 1;
		if( l > r ) swap( l, r );
		int ans = 1;
		for( int i = 1;i <= cnt;i ++ )
			ans = 1ll * ans * mi[i][query( i, l, r )] % mod;
		ans = 1ll * ans * query( rt[l - 1], 1, n, l, r ) % mod;
		printf( "%d\n", lst = ans );
	}
	return 0;
}

Colorful Squares

CodeForces-gym102979-C

二分正方形边长,转化为判断性问题

考虑扫描线

对于大小为 d d d的正方形,先把 x i = x x_i=x xi=x的点加入,再在 x = x i + d + 1 x=x_i+d+1 x=xi+d+1的时候删除

最后判断集合中是否存在一个 y y y,满足 [ y − d , y ] [y-d,y] [yd,y]内的不同颜色种类数达到 k k k

现在先加一个点 [ x 0 , y 0 ] [x_0,y_0] [x0,y0]进集合,那么 [ y 0 − d , y ] [y_0-d,y] [y0d,y]这个区间内就有 c 0 c_0 c0这种颜色

t i t_i ti y = i y=i y=i时被多少种不同颜色覆盖

  • 加一个点 [ x 0 , y 0 ] [x_0,y_0] [x0,y0]对应的就是 [ y 0 − d , y ] [y_0-d,y] [y0d,y]的区间加
  • 删除一个点 [ x 0 , y 0 ] [x_0,y_0] [x0,y0]就是区间减
  • 判断是否存在 k k k种颜色就是区间 m a x \rm max max是否等于 k k k

当然可能 [ y 0 − d , y ] [y_0-d,y] [y0d,y]这个区间之前已经被 c 0 c_0 c0颜色覆盖过一部分,对于之前被覆盖过的区间就不需要操作

所以区间修改前还要调整计算真正的区间

这个调整不会很复杂

因为没被覆盖的区间不会被覆盖过的区间拆分开,没被覆盖的区间一定是完整的一段

#include <set>
#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
#define maxn 250005
#define lson num << 1
#define rson num << 1 | 1
struct tree { int Max, tag; }t[maxn << 2]; //线段树维护区间[l,r]出现不同颜色的种类数 
vector < pair < int, int > > pos[maxn]; //横坐标为x[i]的点集合
multiset < int > col[maxn]; //颜色为c[i]的点的y坐标集合
int n, k;

void pushdown( int num ) {
	t[lson].Max += t[num].tag;
	t[lson].tag += t[num].tag;
	t[rson].Max += t[num].tag;
	t[rson].tag += t[num].tag;
	t[num].tag = 0;
}

void build( int num, int l, int r ) {
	t[num].Max = t[num].tag = 0;
	if( l == r ) return;
	int mid = l + r >> 1;
	build( lson, l, mid );
	build( rson, mid + 1, r );
}

void modify( int num, int l, int r, int L, int R, int x ) {
	if( R < L or R < l or r < L ) return;
	if( L <= l and r <= R ) { t[num].Max += x, t[num].tag += x; return; }
	pushdown( num );
	int mid = l + r >> 1;
	modify( lson, l, mid, L, R, x );
	modify( rson, mid + 1, r, L, R, x );
	t[num].Max = max( t[lson].Max, t[rson].Max );
}

bool check( int d ) {
	for( int i = 0;i <= n;i ++ ) col[i].clear();
	build( 1, 1, n );
	int L, R;
	for( int i = 1;i <= n;i ++ ) {
		for( auto now : pos[i] ) {//枚举横坐标为i的所有点
			int y = now.first, c = now.second;
			/*
			now点覆盖的y坐标为[y-d,y]
			但是这段区间中不能和重复出现过的颜色区间有交
			e.g. 颜色1 覆盖了[2,4] 又有颜色1能覆盖[3,5] 那么只能对[5,5]进行线段树加上颜色1的操作
			因为边长都是d 所以覆盖区间不可能一长一短导致区间分裂 
			每个点影响的区间都是一段完整不分裂的区间 
			*/
			if( col[c].find( y ) != col[c].end() ) {//如果颜色c在纵坐标y的一条横扫描线上出现过 不再加入 
				col[c].insert( y );
				continue;
			}
			else col[c].insert( y );
			//[L,R]表示颜色c的now点真正贡献的区间 接下来就是求解范围限制 
			auto l = col[c].lower_bound( y );//找到颜色c中第一个大于等于y的点位置的纵坐标 
			if( l == col[c].begin() ) L = max( 1, y - d );//没有比现在now的纵坐标更小的点了 下限可以取完 防止溢出1 
			else L = max( *( --l ) + 1, y - d ); //--l第一个小于y的点位置纵坐标+1和覆盖左端点取max 避免重复 
			auto r = col[c].upper_bound( y );//找到颜色中第一个大于y的点 
			if( r == col[c].end() ) R = y;//没有比现在now点纵坐标更大的点了 
			else R = min( *r - d - 1, y );//点的覆盖区间有没有包含现在的now点 
			//要保证覆盖区间是合法的L<=R 这个可以在线段树最开始的时候判断 
			modify( 1, 1, n, L, R, 1 );
		}
		if( i - d - 1 > 0 ) {
			for( auto now : pos[i - d - 1] ) {
			//删掉与现在横坐标距离>d的点 因为是一个一个枚举 每次只用删去距离恰好为d+1的点 更小x的点肯定在前面就被其它的i'删掉了 
				int y = now.first, c = now.second;
				col[c].erase( col[c].find( y ) );
				if( col[c].find( y ) != col[c].end() ) continue;//如果纵坐标y这里还有点 那么此时删去的点对线段树内值不会造成影响
				//否则就同样的找到颜色c的这个now点真正贡献的区间
				auto l = col[c].lower_bound( y );
				if( l == col[c].begin() ) L = max( 1, y - d );
				else L = max( *( --l ) + 1, y - d );
				auto r = col[c].upper_bound( y );
				if( r == col[c].end() ) R = y;
				else R = min( *r - d - 1, y );
				modify( 1, 1, n, L, R, -1 );
			}
		}
		if( t[1].Max == k ) return 1;
	}
	return 0;
}

int main() {
	scanf( "%d %d", &n, &k );
	for( int i = 1, x, y, c;i <= n;i ++ ) {
		scanf( "%d %d %d", &x, &y, &c );
		pos[x].push_back( make_pair( y, c ) );
	}
	n = maxn - 5;
	int l = 0, r = n, ans;
	while( l <= r ) {
		int mid = l + r >> 1;
		if( check( mid ) ) ans = mid, r = mid - 1;
		else l = mid + 1;
	}
	printf( "%d\n", ans );
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值