莫队

莫队

普通莫队

假设n=m 那么对于序列上的区间询问问题 如果从[l,r]可以O(1)扩展到四个和[l,r]相邻的区间 那么我们就可以在O(nn)的复杂度内求出所有询问的答案

实现方法为:离线后排序 顺序处理每个询问 暴力从上一个区间的答案转移到下一个区间的答案(一步一步移动即可)

排序策略:先按照询问的左端点所在的块排序 再按照询问右端点排序

P2709 小B的询问

非常naive的莫队 转移就是完全平方柿展开

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
const int N = 5e4 + 5;

int read ()
{
	int x = 0 , f = 1;
	char ch = cin.get();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
	return x * f;
}

int block , ans[N] , res , b[N] , n , m , k , col[N];

struct node { int l , r , id , pos; } a[N];

void add ( int x )
{
	res += 2 * b[x] + 1;
	b[x] ++;
}

void del ( int x ) 
{
	res -= 2 * b[x] - 1;
	b[x] --;
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , m = read() , k = read();
	block = sqrt(n);
	for ( int i = 1 ; i <= n ; i ++ ) col[i] = read();
	for ( int i = 1 ; i <= m ; i ++ ) a[i].l = read() , a[i].r = read() , a[i].id = i , a[i].pos = ( a[i].l - 1 ) / block + 1;
	sort ( a + 1 , a + m + 1 , [](const node &a , const node &b) { return a.pos == b.pos ? a.r < b.r : a.pos < b.pos; } );\
	int l = 1 , r = 0;
	for ( int i = 1 ; i <= n ; i ++ )
	{
		while ( l > a[i].l ) add ( col[--l] );
		while ( r < a[i].r ) add ( col[++r] );
		while ( l < a[i].l ) del ( col[l++] );
		while ( r > a[i].r ) del ( col[r--] );
		ans[a[i].id] = res;
	}
	
	for ( int i = 1 ; i <= m ; i ++ ) cout << ans[i] << endl;
	return 0;
}

P1494 [国家集训队] 小 Z 的袜子

对于[l,r]区间内的袜子对数的询问 设其中颜色x,y,z的袜子个数a,b,c 那么答案就是(a(a1)/2+b(b1)/2+c(c1)/2+)/((rl+1)(rL)/2) 其中分子为相同颜色袜子的对数 分母为区间内所有袜子的对数

化简 上下同除2 将括号打开 得到原式=(a2+b2+c2+(rl+1))/((rl+1)(rl))

那么我们只需要求一个区间内每一种颜色数目的平方和即可

注意pos的求法TLE的时候需要先检查算法复杂度

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 5e4 + 10;

int read ()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

inline int gcd ( int a , int b )
{
	if ( a % b == 0 ) return b;
	return gcd ( b , a % b );
}

int n , m , block , b[N] , res , col[N]; 

struct node { int l , r , id , pos; } a[N];
struct anss { int a , b; } ans[N];

inline void add ( int x )
{
	res += b[x] * 2 + 1;
	++ b[x];
}

inline void del ( int x )
{
	res -= b[x] * 2 - 1;
	-- b[x];
}

signed main ()
{
//	ios::sync_with_stdio(false);
//	cin.tie(0) , cout.tie(0);
	n = read() , m = read(); block = (int)sqrt(n);
	for ( int i = 1 ; i <= n ; i ++ ) col[i] = read();
	for ( int i = 1 ; i <= m ; i ++ ) a[i].l = read() , a[i].r = read() , a[i].id = i , a[i].pos = ( a[i].l - 1 ) / block + 1;
	sort ( a + 1 , a + m + 1 , [](const node &a , const node &b) { return a.pos == b.pos ? a.r > b.r : a.pos < b.pos; } );
	int l = 1 , r = 0;
	for ( int i = 1 ; i <= m ; i ++ )
	{
		while ( a[i].l < l ) add ( col[--l] );
		while ( r < a[i].r ) add ( col[++r] );
		while ( l < a[i].l ) del ( col[l++] );
		while ( a[i].r < r ) del ( col[r--] );
		if ( a[i].l == a[i].r ) { ans[a[i].id] = { 0 , 1 }; continue; }
		int tempa = res - ( a[i].r - a[i].l + 1 );
		int tempb = ( a[i].r - a[i].l + 1 ) * ( a[i].r - a[i].l );
		int tt = gcd ( tempa , tempb );
		ans[a[i].id] = { tempa / tt , tempb / tt };
	}
	for ( int i = 1 ; i <= m ; i ++ ) printf ( "%lld/%lld\n" , ans[i].a , ans[i].b );
	return 0;
}

P1972 [SDOI2009] HH的项链

60pts代码 权当莫队训练了

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
const int N = 1e6 + 10;

inline int read ()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}
inline void wr(int x){if (x<0) {putchar('-');wr(-x);return;}if(x>=10)wr(x/10);putchar(x%10+'0');}
inline void wrn(int x){wr(x);putchar('\n');}inline void wri(int x){wr(x);putchar(' ');}


int n , m , block , b[N] , res , col[N] , ans[N];

struct node { int l , r , id , pos; } a[N];

inline void add ( int x ) { res += ( ++b[x] == 1 ); }
inline void del ( int x ) { res -= ( --b[x] == 0 ); }

signed main ()
{
	n = read();
	for ( int i = 1 ; i <= n ; i ++ ) col[i] = read();
	m = read();
	block = n / sqrt(2*m/3);
	for ( int i = 1 ; i <= m ; i ++ ) a[i].l = read() , a[i].r = read() , a[i].id = i , a[i].pos = ( a[i].l - 1 ) / block + 1;
	sort ( a + 1 , a + m + 1 , [](const node &a , const node &b) { return a.pos==b.pos?(a.pos&1)?a.r<b.r:a.r>b.r:a.pos<b.pos;} );
	int l = 1 , r = 0;
	for ( int i = 1 ; i <= m ; i ++ )
	{
		while ( a[i].l < l ) add ( col[--l] );
		while ( r < a[i].r ) add ( col[++r] );
		while ( l < a[i].l ) del ( col[l++] );
		while ( a[i].r < r ) del ( col[r--] );
		ans[a[i].id] = res;
	}
	for ( int i = 1 ; i <= m ; i ++ ) wrn(ans[i]);
	return 0;
}

P4462 [CQOI2018] 异或序列

还是经典的莫队操作 先将询问排序

有一个性质:如果ab=c 那么ac=bbc=a

所以我们考虑维护a[i]为异或前缀和 那么对于一个区间[l,r] 这个区间满足条件当且仅当a[l1]a[r]=k

那么我们对于[l,r]区间 可以在莫队中用桶维护整个[l1,r]区间中前缀和为a[i]的数量之和

此时对于答案的贡献就是b[a[x]k]

因为我们维护的是[l1,r]区间内的信息 那么我们操作左端点的时候需要操作l1而不是l

b[0]=1的原因:我们初始值队列中l=1,r=0 那么我们应该插入一个l1的值 也就是0

下面给出两种写法

第一种:

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
constexpr int N = 1e6 + 5;
char buf[1<<22] , *p1 , *p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<22,stdin),p1==p2)?EOF:*p1++)
int read ()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , m , k , block , a[N] , b[N] , res , ans[N];

struct DQY { int l , r , id , pos; } q[N];

void upd ( int x ) { b[a[x]] ++ , res += b[a[x]^k]; }
void del ( int x ) { res -= b[a[x]^k] , b[a[x]] --; }

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , m = read() , k = read() , block = sqrt(n);
	for ( int i = 1 ; i <= n ; i ++ ) a[i] = read() , a[i] ^= a[i-1];
	for ( int i	= 1 ; i <= m ; i ++ ) q[i].l = read() , q[i].r = read() , q[i].id = i , q[i].pos = ( q[i].l - 1 ) / block + 1;
	sort ( q + 1 , q + m + 1 , [](const DQY &a , const DQY &b) { return a.pos == b.pos ? ( ( a.pos & 1 ) ? a.r < b.r : a.r > b.r ) : a.pos < b.pos; } );
	int l = 1 , r = 0; b[0] = 1;
	for ( int i = 1 ; i <= m ; i ++ )
	{
		while ( q[i].l < l ) -- l , upd(l-1);
		while ( r < q[i].r ) upd(++r);
		while ( l < q[i].l ) del(l-1) , l ++;
		while ( q[i].r < r ) del(r--);
		ans[q[i].id] = res;
	}
	for ( int i = 1 ; i <= m ; i ++ ) cout << ans[i] << endl;	
	return 0;
}

也可以是下面一种实现方式:

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
constexpr int N = 1e6 + 5;
char buf[1<<22] , *p1 , *p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<22,stdin),p1==p2)?EOF:*p1++)
//#define getchar() cin.get()
int read ()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , m , k , block , a[N] , b[N] , res , ans[N];

struct DQY { int l , r , id , pos; } q[N];

void updl ( int x ) { b[a[x-1]] ++ , res += b[a[x-1]^k]; }
void updr ( int x ) { b[a[x]] ++ , res += b[a[x]^k]; }
void dell ( int x ) { res -= b[a[x-1]^k] , b[a[x-1]] --; }
void delr ( int x ) { res -= b[a[x]^k] , b[a[x]] --; }

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , m = read() , k = read() , block = sqrt(n);
	for ( int i = 1 ; i <= n ; i ++ ) a[i] = read() , a[i] ^= a[i-1];
	for ( int i	= 1 ; i <= m ; i ++ ) q[i].l = read() , q[i].r = read() , q[i].id = i , q[i].pos = ( q[i].l - 1 ) / block + 1;
	sort ( q + 1 , q + m + 1 , [](const DQY &a , const DQY &b) { return a.pos == b.pos ? ( ( a.pos & 1 ) ? a.r < b.r : a.r > b.r ) : a.pos < b.pos; } );
	int l = 1 , r = 0; b[0] = 1;
	for ( int i = 1 ; i <= m ; i ++ )
	{
		while ( q[i].l < l ) updl(--l);
		while ( r < q[i].r ) updr(++r);
		while ( l < q[i].l ) dell(l++);
		while ( q[i].r < r ) delr(r--);
		ans[q[i].id] = res;
	}
	for ( int i = 1 ; i <= m ; i ++ ) cout << ans[i] << endl;	
	return 0;
}

P4396 [AHOI2013] 作业

莫队+分块

一道很好的综合题

90pts做法:O(nnlogn) 吸氧可过

维护两个树状数组 分别为第一个和第二个问题的答案 莫队查询即可

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
constexpr int N = 1e6 + 5;
//char buf[1<<22] , *p1 , *p2;
//#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<22,stdin),p1==p2)?EOF:*p1++)
#define getchar() cin.get() 
int read ()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , m;
int block , a[N] , b[N];

struct query { int l , r , a , b , id , pos; } q[N];

struct DQY { int ans1 , ans2; } ans[N];//你是我的答案=v= 

struct bit
{
	int t[N];
	inline int lowbit ( int x ) { return x & (-x); }
	void upd ( int x , int val ) { for ( ; x <= n ; x += lowbit(x) ) t[x] += val; }
	int query ( int x ) { int res = 0; for ( ; x ; x -= lowbit(x) ) res += t[x]; return res; }
}bit1 , bit2;

void upd ( int x )
{
	bit1.upd ( a[x] , 1 );
	if ( ++ b[a[x]] == 1 ) bit2.upd ( a[x] , 1 ); 
} 

void del ( int x ) 
{
	bit1.upd ( a[x] , -1 );
	if ( -- b[a[x]] == 0 ) bit2.upd ( a[x] , -1 );
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , m = read() , block = sqrt(n);
	for ( int i = 1 ; i <= n ; i ++ ) a[i] = read();
	for ( int i = 1 ; i <= m ; i ++ ) q[i].l = read() , q[i].r = read() , q[i].a = read() , q[i].b = read() , q[i].id = i , q[i].pos = ( ( q[i].l - 1 ) / block + 1 );
	sort ( q + 1 , q + m + 1 , [](const query &a , const query &b) { return a.pos == b.pos ? a.r < b.r : a.pos < b.pos; } ); 
	int l = 1 , r = 0;
	for ( int i = 1 ; i <= m ; i ++ )
	{
		while ( q[i].l < l ) upd(--l);
		while ( r < q[i].r ) upd(++r);
		while ( l < q[i].l ) del(l++);
		while ( q[i].r < r ) del(r--);
		ans[q[i].id].ans1 = bit1.query ( q[i].b ) - bit1.query ( q[i].a - 1 );
		ans[q[i].id].ans2 = bit2.query ( q[i].b ) - bit2.query ( q[i].a - 1 );
	}
	for ( int i = 1 ; i <= m ; i ++ ) cout << ans[i].ans1 << ' ' << ans[i].ans2 << endl;	
	return 0;
}

100pts:莫队+分块

同上 将树状数组修改O(nlogn)改为每次分块查询O(n) 降低了时间复杂度

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
constexpr int N = 1e5 + 5;
//char buf[1<<22] , *p1 , *p2;
//#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<22,stdin),p1==p2)?EOF:*p1++)
#define getchar() cin.get() 
int read ()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , m;
int block , ll[N] , rr[N] , a[N] , bel[N] , a1[N] , a2[N] , sum1[N] , sum2[N];

struct query { int l , r , a , b , id , pos; } q[N];

struct DQY { int ans1 , ans2; } ans[N];//你是我的答案=v= 

void upd ( int x )
{
	a1[a[x]] ++ , sum1[bel[a[x]]] ++;
	if ( a1[a[x]] == 1 ) a2[a[x]] ++ , sum2[bel[a[x]]] ++;
} 

void del ( int x ) 
{
	a1[a[x]] -- , sum1[bel[a[x]]] --;
	if ( a1[a[x]] == 0 ) a2[a[x]] -- , sum2[bel[a[x]]] --;
}

void getans ( int x , int y , int k )
{
	int xx = bel[x] , yy = bel[y];
	if ( xx == yy ) { for ( int i = x ; i <= y ; i ++ ) ans[k].ans1 += a1[i] , ans[k].ans2 += a2[i]; return; }
	for ( int i = xx + 1 ; i <= yy - 1 ; i ++ ) ans[k].ans1 += sum1[i] , ans[k].ans2 += sum2[i];
	for ( int i = x ; i <= rr[xx] ; i ++ ) ans[k].ans1 += a1[i] , ans[k].ans2 += a2[i];
	for ( int i = ll[yy] ; i <= y ; i ++ ) ans[k].ans1 += a1[i] , ans[k].ans2 += a2[i];
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , m = read() , block = sqrt(n);
	int tot = n / block;
	if ( n % block ) tot ++;
	for ( int i = 1 ; i <= n ; i ++ ) a[i] = read() , bel[i] = ( i - 1 ) / block + 1;
	for ( int i = 1 ; i <= m ; i ++ ) q[i].l = read() , q[i].r = read() , q[i].a = read() , q[i].b = read() , q[i].id = i , q[i].pos = bel[q[i].l];
	for ( int i = 1 ; i <= tot ; i ++ ) ll[i] = ( i - 1 ) * block + 1 , rr[i] = i * block;
	sort ( q + 1 , q + m + 1 , [](const query &a , const query &b) { return a.pos == b.pos ? ( (a.pos&1) ? a.r < b.r : a.r > b.r ) : a.pos < b.pos; } ); 
	int l = 1 , r = 0;
	for ( int i = 1 ; i <= m ; i ++ )
	{
		while ( q[i].l < l ) upd(--l);
		while ( r < q[i].r ) upd(++r);
		while ( l < q[i].l ) del(l++);
		while ( q[i].r < r ) del(r--);
		getans ( q[i].a , q[i].b , q[i].id );
	}
	for ( int i = 1 ; i <= m ; i ++ ) cout << ans[i].ans1 << ' ' << ans[i].ans2 << endl;
	return 0;
}

带修莫队

P1903 [国家集训队] 数颜色 / 维护队列

xswl 一个小问题调了50min 请注意看如下两行代码:

a[cnta].id = ++cnta , a[cnta].l = read() , a[cnta].r = read() , a[cnta].t = cntq;
a[++cnta].id = cnta , a[cnta].l = read() , a[cnta].r = read() , a[cnta].t = cntq;

乍一看没什么区别 但是我们可以看到前一个代码事正确的 而后面的代码错误

因为赋值号是先从右面的表达式开始计算 所以如果要实现读入询问和id的话 需要先自增cnta再赋值

upd on 7.24 21:37 又被这个UB卡了 这回用了上面的写法但是本地编译器跑样例不过 交luoguAC

所以最正确的代码是:(上面这两行代码都不推荐采用 不知道编译器什么时候会卡你)

++cnta , a[cnta].id = cnta , a[cnta].l = read() , a[cnta].r = read() , a[cnta].t = cntq;

进入正题 带修莫队是在普通莫队基础上加了一维时间维度来实现的

我们先读入数据 排序策略为:先按照l所在的块排序 再按照r所在的块排序 最后按照t排序 (均为从小到大)

在修改的时候 我们也要比普通莫队多一次操作: 根据时间进行upd

也就是在当前区间的t和上一个区间的t之间的所有修改操作都需要累加进来

这里有一个小优化:我们对于每一次修改操作 假设这次修改操作是将col[i]改成c 那么col[i]现在改成c了 那么下一次我们一定要将col[i]改成原来的col[i]

所以我们每次将修改操作的c和要修改的col[i] 交换一下 下一次再累加 这样就避免了写另一个函数

对于多次使用的操作 内联函数要多加

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long 
constexpr int N = 3e6 + 5;
//char buf[1<<24] , *p1 , *p2;
//#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
int read ()
{
	int x = 0 , f = 1;
	char ch = cin.get();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
	return x * f;
}

int n , m , block;
int cntc , cntq , a[N] , b[N] , ans[N] , bel[N] , res;

struct DQY { int l , r , id , t; } q[N];//你是我的答案=v=
struct change { int p , c; } c[N];

inline void add ( int x ) { res += ( ++ b[x] == 1 ); }
inline void del ( int x ) { res -= ( -- b[x] == 0 ); }
inline void upd ( int x , int t ) { if ( q[x].l <= c[t].p && c[t].p <= q[x].r ) add ( c[t].c ) , del ( a[c[t].p] ); swap ( c[t].c , a[c[t].p] ); }

char ch;
signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , m = read() , block = pow ( n , 0.666 );
	for ( int i = 1 ; i <= n ; i ++ ) a[i] = read() , bel[i] = ( i - 1 ) / block + 1;
	for ( int i = 1 ; i <= m ; i ++ )
	{
		cin >> ch;
		if ( ch == 'Q' ) q[cntq].id = ++cntq , q[cntq].l = read() , q[cntq].r = read() , q[cntq].t = cntc;//t记录的是前面有几个修改操作	
		else c[++cntc].p = read() , c[cntc].c = read();
	}
	sort ( q + 1 , q + cntq + 1 , [](const DQY &a , const DQY &b) { return bel[a.l] == bel[b.l] ? ( bel[a.r] == bel[b.r] ? a.t < b.t : bel[a.r] < bel[b.r] ) : bel[a.l] < bel[b.l]; } );
	int l = 1 , r = 0 , t = 0;
	for ( int i = 1 ; i <= cntq ; i ++ )
	{
		while ( q[i].l < l ) add(a[--l]);
		while ( r < q[i].r ) add(a[++r]);
		while ( l < q[i].l ) del(a[l++]);
		while ( q[i].r < r ) del(a[r--]);
		while ( t < q[i].t ) upd(i,++t);
		while ( q[i].t < t ) upd(i,t--);
		ans[q[i].id] = res;
	}
	for ( int i = 1 ; i <= cntq ; i ++ ) cout << ans[i] << endl; 
	return 0;
}

bitset优化莫队:

P3674 小清新人渣的本愿

bitset的妙用

首先 我们对于1maxn开两个bitset 分别为now1now2

now1[i]维护区间内有没有值为i的数 now2[i]维护区间内有没有值为maxni的数

对于1操作 我们需要找到有没有zy=x的数 那么将now1右移x位并与now1做与运算 如果有为1的值 那么存在这样的数 这个操作的复杂度是O(nw) 一般w64

对于2操作 需要找出一组z+y=x 那么可以变换成maxnzy=maxnx 然后将maxnz看成一个整体z 问题就转化成了zy=maxnx1操作处理

对于3操作 需要枚举所有因数 在bitset中查找是否有这两个数即可

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
constexpr int maxn = 1e5;
constexpr int N = 1e5 + 5;
char buf[1<<22] , *p1 , *p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<22,stdin),p1==p2)?EOF:*p1++)
//#define getchar() cin.get() 
int read ()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , m;
int block , a[N] , b[N] , ans[N];

bitset<N> now1 , now2;

struct query { int op , l , r , id , x , pos; } q[N];

void upd ( int x ) { if ( ++ b[x] == 1 ) now1[x] = 1 , now2[maxn-x] = 1; }
void del ( int x ) { if ( -- b[x] == 0 ) now1[x] = 0 , now2[maxn-x] = 0; }

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , m = read() , block = sqrt(n);
	for ( int i = 1 ; i <= n ; i ++ ) a[i] = read();
	for ( int i = 1 ; i <= m ; i ++ ) q[i].op = read() , q[i].l = read() , q[i].r = read() , q[i].x = read() , q[i].id = i , q[i].pos = ( q[i].l - 1 ) / block + 1;
	sort ( q + 1 , q + m + 1 , [](const query &a , const query &b) { return a.pos == b.pos ? a.r < b.r : a.pos < b.pos; } ); 
	int l = 1 , r = 0;
	for ( int i = 1 ; i <= m ; i ++ )
	{
		while ( q[i].l < l ) upd(a[--l]);
		while ( r < q[i].r ) upd(a[++r]);
		while ( l < q[i].l ) del(a[l++]);
		while ( q[i].r < r ) del(a[r--]);
		int x = q[i].x;
		if ( q[i].op == 1 ) if ( ( now1 & ( now1 >> x ) ).any() ) ans[q[i].id] = 1;
		if ( q[i].op == 2 )	if ( ( now1 & ( now2 >> maxn - x ) ).any() ) ans[q[i].id] = 1;
		if ( q[i].op == 3 )
		{
			for ( int j = 1 ; j * j <= x ; j ++ )
				if ( ! ( x % j ) && now1[x/j] && now1[j] ) { ans[q[i].id] = 1; break; }
		}
	}
	for ( int i = 1 ; i <= m ; i ++ ) cout << ( ans[i] ? "hana" : "bi" ) << endl;
	return 0;
}

P4688 [Ynoi2016] 掉进兔子洞

答案显然是i=13(rili+1)3j=1maxnmin(cnt1[j],cnt2[j],cnt3[j])

前面的部分很好办 在输入的时候统计即可 现在考虑后面

个人理解是无法做到排序使得三个询问区间绑在一起处理 所以三个区间要分开处理 最后再合并

离散化ai是必须的 但是因为bitset只能维护01 所以我们离散化数组不能去重

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
constexpr int maxn = 1e5;
constexpr int N = 1e5 + 5;
constexpr int T = 25000;
char buf[1<<22] , *p1 , *p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<22,stdin),p1==p2)?EOF:*p1++)
//#define getchar() cin.get() 
int read ()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , M , block;
int cnt , a[N] , b[N] , ans[N] , bel[N];

bitset<N> f[T] , res;
vector<int> lsh;

struct DQY { int l , r , id; } q[N];//你是我的答案=v= 

void upd ( int x ) { res.set ( x + b[x] ) , ++b[x]; }
void del ( int x ) { --b[x] , res.reset ( x + b[x] ); }

void solve ( int m )
{
	cnt = 0 , memset ( b , 0 , sizeof b ) , res.reset();
	for ( int i = 1 ; i <= m ; i ++ )
	{
		f[i].set() , ans[i] = 0;
		for ( int j = 1 ; j <= 3 ; j ++ ) 
			cnt ++ , q[cnt].l = read() , q[cnt].r = read() , q[cnt].id = i , ans[i] += q[cnt].r - q[cnt].l + 1;
	}
	sort ( q + 1 , q + cnt + 1 , [](const DQY &a , const DQY &b) { return bel[a.l] == bel[b.l] ? ( bel[a.l] & 1 ? a.r > b.r : a.r < b.r ) : bel[a.l] < bel[b.l]; } ); 
	int l = 1 , r = 0;
	for ( int i = 1 ; i <= cnt ; i ++ )
	{
		while ( q[i].l < l ) upd(a[--l]);
		while ( r < q[i].r ) upd(a[++r]);
		while ( l < q[i].l ) del(a[l++]);
		while ( q[i].r < r ) del(a[r--]);
		f[q[i].id] &= res;
	}
	for ( int i = 1 ; i <= m ; i ++ ) cout << ans[i] - f[i].count() * 3 << endl;
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , M = read() , block = sqrt(n);
	for ( int i = 1 ; i <= n ; i ++ ) a[i] = read() , bel[i] = ( i - 1 ) / block + 1 , lsh.push_back(a[i]);
	sort ( lsh.begin() , lsh.end() );
	for ( int i = 1 ; i <= n ; i ++ ) a[i] = lower_bound ( lsh.begin() , lsh.end() , a[i] ) - lsh.begin() + 1;
	for ( int i = 1 ; i <= M ; i += T ) solve ( min ( i + T - 1 , M ) - i + 1 );
	return 0;
}

回滚莫队

主要适用范围为:

  1. 加点操作易实现 删除操作难以实现(例如维护一个集合内的最大值)
  2. 删除操作易实现 加点操作难以实现(例如维护一个集合内最小未出现的自然数)

歴史の研究

隆重介绍这样一道写了一个上午+半个下午的题 三次更改数组离散化和vector离散化 五次将题解代码块粘入程序中统计 十余次将程序运行过程和题解运行过程比较 最后发现——

对于每一个暴力的块(右端点在左块内) 需要单独开一个桶来记录其中信息 不能和其他块(右端点在左块外)混为一谈

怄火 恼怒 转圈 破防了 为什么我这么菜啊

upd on 8.12: 更新了写法 但是因为adddel函数的返回值写成了int导致一直RE调不出来 焯


这道题是上面的第一类问题 对于加点操作直接操作即可 但是对于删除操作 我们删除之后难以知道次大值的值

我们还是按照普通莫队对询问排序 左端点的块单调递增 左端点相同的 右端点单调递增

那么对于询问 有两种情况:

  • 右端点在左块内

    这时候我们可以直接暴力求 因为块长最多n

  • 右端点在左块外

    根据我们的排序策略 右块内的部分是持续增加的 真正涉及删除操作的是左端点

那么我们对于一些左端点在同一个块内的询问 先将l指针设置为该块的右端点+1r指针设置为该块的右端点

然后对于一个区间 进行如下操作:

  1. 先扩展右端点并记录一下扩展后的答案
  2. l指针位置开始向左扩展左端点 为ans数组赋值
  3. 再复原回扩展左端点前的答案 以便下一次扩展左端点
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
#define print(x) cout << #x << '=' << x << endl
constexpr int N = 3e6 + 5;
constexpr int inf = 0x3f3f3f3f3f3f3f3f;
//char buf[1<<24] , *p1 , *p2;
//#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
int read ()
{
	int x = 0 , f = 1;
	char ch = cin.get();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
	return x * f;
}

int n , m , block , tot;
int a[N] , b[N] , bb[N] , ll[N] , rr[N] , ans[N] , bel[N] , res , lstblock;

struct DQY { int l , r , id; } q[N];//你是我的答案=v=
vector<int>lsh;
inline void add ( int x ) { b[x] ++ , res = max ( res , b[x] * lsh[x] ); }
inline void del ( int x ) { b[x] --; }

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , m = read();
	lsh.push_back(-inf);
	for ( int i = 1 ; i <= n ; i ++ ) a[i] = read() , lsh.push_back(a[i]);
	sort ( lsh.begin() , lsh.end() );
	lsh.erase ( unique ( lsh.begin() , lsh.end() ) , lsh.end() );
	for ( int i = 1 ; i <= n ; i ++ ) a[i] = lower_bound ( lsh.begin() , lsh.end() , a[i] ) - lsh.begin();
	
	block = pow ( n , 0.5 ) , tot = n / block;
	if ( n % block ) tot ++;
	for ( int i = 1 ; i <= n ; i ++ ) bel[i] = ( i - 1 ) / block + 1;
	for ( int i = 1 ; i <= tot ; i ++ ) ll[i] = ( i - 1 ) * block + 1 , rr[i] = i * block;
	rr[tot] = n;
	
	for ( int i = 1 ; i <= m ; i ++ ) q[i].l = read() , q[i].r = read() , q[i].id = i;
	sort ( q + 1 , q + m + 1 , [](const DQY &a , const DQY &b) { return bel[a.l] == bel[b.l] ? a.r < b.r : bel[a.l] < bel[b.l]; } );
	
	int l = 1 , r = 0;
	for ( int i = 1 ; i <= m ; i ++ )
	{
		if ( bel[q[i].l] == bel[q[i].r] )//必须用一个不同的"bb"数组来存储暴力统计的答案! 
		{
			int temp = 0;
			for ( int j = q[i].l ; j <= q[i].r ; j ++ ) bb[a[j]] ++;
			for ( int j = q[i].l ; j <= q[i].r ; j ++ ) temp = max ( temp , bb[a[j]] * lsh[a[j]] );
			for ( int j = q[i].l ; j <= q[i].r ; j ++ ) bb[a[j]] --;
			ans[q[i].id] = temp;
			continue;
		}
		if ( lstblock ^ bel[q[i].l] )
		{
			while ( r > rr[bel[q[i].l]] ) del(a[r--]);
            while ( l < rr[bel[q[i].l]]+1 ) del(a[l++]);
            res = 0 , lstblock = bel[q[i].l];	
		}
		while ( r < q[i].r ) add(a[++r]);
		int temp = res , l_ = l;
		while ( l_ > q[i].l ) add(a[--l_]);
		while ( l_ < l ) del(a[l_++]);
		ans[q[i].id] = res;
		res = temp;
	}
	for ( int i = 1 ; i <= m ; i ++ ) cout << ans[i] << endl; 
	return 0;
}

P4137 Rmq Problem / mex

这道题是回滚莫队的第二种情况 删除操作很易实现 但是加点操作不易实现

类比上一道题 我们可以先将整个序列先加进去 然后进行删点操作

注意此时的排序在左端点所在的块相同的时候需要按照右端点降序排序 以便删除区间

初始值l=1r=n

我们对于第一个块 l指针设置为块的左端点 r指针设置为n即可

类比上一道题 进行如下操作:

  1. 先收缩右端点并记录一下收缩后的答案
  2. l指针位置开始向右收缩左端点 为ans数组赋值
  3. 再复原回收缩左端点前的答案 以便下一次收缩左端点
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
constexpr int N = 3e6 + 5;
//#define int long long 
//char buf[1<<22] , *p1 , *p2;
//#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<22,stdin),p1==p2)?EOF:*p1++)
#define getchar() cin.get() 
int read ()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}

int n , m , block , tot;
int a[N] , b[N] , bb[N] , ll[N] , rr[N] , ans[N] , bel[N] , res , lstblock;

struct DQY { int l , r , id; } q[N];//你是我的答案=v=

inline void add ( int x ) { if ( x > n + 1 ) return; b[x] ++; }//?
inline void del ( int x ) { if ( x > n + 1 ) return; b[x] --; if ( !b[x] ) res = min ( res , x ); }

signed main ()
{
//	freopen ( "P4137_1.in" , "r" , stdin );
	ios::sync_with_stdio(false);
	cin.tie(nullptr) , cout.tie(nullptr);
	n = read() , m = read();
	for ( int i = 1 ; i <= n ; i ++ ) a[i] = read();
	
	block = sqrt(n) , tot = n / block;
	if ( n % block ) tot ++;
	for ( int i = 1 ; i <= n ; i ++ ) bel[i] = ( i - 1 ) / block + 1;
	for ( int i = 1 ; i <= tot ; i ++ ) ll[i] = ( i - 1 ) * block + 1 , rr[i] = i * block;
	rr[tot] = n;
	for ( int i = 1 ; i <= m ; i ++ ) q[i].l = read() , q[i].r = read() , q[i].id = i;
	sort ( q + 1 , q + m + 1 , [](const DQY &a , const DQY &b) { return bel[a.l] == bel[b.l] ? a.r > b.r : bel[a.l] < bel[b.l]; } );
	
	for ( int i = 1 ; i <= n ; i ++ ) b[a[i]] ++;
	while ( b[res] ) res ++;
	//先将所有东西都加入队列中 
	int st = res;
	
	int l = 1 , r = n; 
	for ( int i = 1 ; i <= m ; i ++ )
	{
		if ( bel[q[i].l] == bel[q[i].r] )
		{
			int tmp = 0;
			for ( int j = q[i].l ; j <= q[i].r ; j ++ ) if ( a[j] <= n + 1 ) bb[a[j]] ++;
			while ( bb[tmp] ) tmp ++;
			for ( int j = q[i].l ; j <= q[i].r ; j ++ ) if ( a[j] <= n + 1 ) bb[a[j]] --;
			ans[q[i].id] = tmp;
			continue;
		}
		if ( bel[q[i].l] ^ lstblock )
		{
			res = st;
			while ( r < n ) add(a[++r]);
			while ( l < ll[bel[q[i].l]] ) del(a[l++]);
			st = res , lstblock = bel[q[i].l];
		}
		while ( q[i].r < r ) del(a[r--]);
		int temp = res , l_ = l;
		while ( l_ < q[i].l ) del(a[l_++]);
		while ( l_ > l ) add(a[--l_]);
		ans[q[i].id] = res;		
		res = temp;
	}
	for ( int i = 1 ; i <= m ; i ++ ) cout << ans[i] << endl;
	return 0;
}

贴一个主席树做法捏()

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define mid (l+r>>1)
#define ls(p) t[p].son[0]
#define rs(p) t[p].son[1]
#define lson ls(p),l,mid
#define rson rs(p),mid+1,r
constexpr int N = 3e6 + 5;
char buf[1<<24] , *p1 , *p2;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<24,stdin),p1==p2)?EOF:*p1++)
//#define getchar() cin.get() 
int read ()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar(); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
	return x * f;
}
int n , m , a[N] , root[N];

vector<int> lsh;
struct DQY 
{
	struct node { int son[2] , minn; } t[N<<5];
	int tot = 0;
	inline int new_node ( int p ) { t[++tot] = t[p]; return tot; }
	inline void up ( int p ) { t[p].minn = min ( t[ls(p)].minn , t[rs(p)].minn ); }
	void upd ( int &p , int l , int r , int x , int val )
	{
		p = new_node(p);
		if ( l == r ) return t[p].minn = val , void();
		if ( x <= mid ) upd ( lson , x , val );
		else upd ( rson , x , val );
		up(p);
	}
	int query ( int p , int l , int r , int x )
	{
		if ( l == r ) return l;
		if ( t[ls(p)].minn < x ) return query ( lson , x );
		else return query ( rson , x );
	}
}T;

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , m = read();
	lsh.push_back(0);
	for ( int i = 1 ; i <= n ; i ++ ) a[i] = read() , lsh.push_back(a[i]) , lsh.push_back(a[i]+1);
	sort ( lsh.begin() , lsh.end() );
	lsh.erase ( unique ( lsh.begin() , lsh.end() ) , lsh.end() );
	int sz = lsh.size();
	for ( int i = 1 ; i <= n ; i ++ ) a[i] = lower_bound ( lsh.begin() , lsh.end() , a[i] ) - lsh.begin();
	for ( int i = 1 ; i <= n ; i ++ ) root[i] = root[i-1] , T.upd ( root[i] , 0 , sz , a[i] , i );
	for ( int i = 1 ; i <= m ; i ++ ) 
	{
		int l = read() , r = read();
		cout << lsh[T.query ( root[r] , 0 , sz , l )] << endl;
	}
	return 0;
}
posted @   Echo_Long  阅读(20)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示