Ds100p -「数据结构百题」61~70

cirnovsky /

☆ 61.P5355 [Ynoi2017]由乃的玉米田

由乃在自己的农田边散步,她突然发现田里的一排玉米非常的不美。

这排玉米一共有 NN 株,它们的高度参差不齐。

由乃认为玉米田不美,所以她决定出个数据结构题

这个题是这样的:

给你一个序列 aa,长度为 nn,有 mm 次操作,每次询问一个区间是否可以选出两个数它们的差为 xx,或者询问一个区间是否可以选出两个数它们的和为 xx,或者询问一个区间是否可以选出两个数它们的乘积为 xx ,或者询问一个区间是否可以选出两个数它们的商为 xx(没有余数) ,这四个操作分别为操作 1,2,3,41,2,3,4

选出的这两个数可以是同一个位置的数。


§ 题意简述

见题面

§ 题解

前三个操作就是小清新人渣的本愿。

这里简单讲解一下。

记录两个 bitset clainv

我们考虑莫队。

cla[x]==1 表示 xx 这个数出现过。

inv[x]==1 表示 100000x100000-x 这个数出现过。

这两个 bitset 的维护很简单,就是在莫队的加减贡献操作里改就行了。

对于第一个减法的判断,我们的答案就是 ((cla<<x)&cla) 是否为 0。

如果为 0 的话表示应该输出有解。

正确性很好得到。

比如我们的询问是是否存在 a,ba,b 使得 ab=xa-b=x

那么我们只需要存在 aa 以及 axa-x 即可。

第二个加法的判断也差不多,看作是加一个负数即可,判断是 ((cla<<(100000-x)&inv))

第三个乘法的判断直接暴力枚举因子 ii,判断 i,xii,\frac{x}{i} 是否同时存在即可。(ixi\mid x)。

由于值域和 n,mn,m 同阶,所以我们的复杂度是对的。

对于第四个操作我们直接从乘法贺过来。

枚举一个 ii,从 1 开始,终止条件为 i×x100000i\times x\le100000

其中 xx 为当前询问给出的商。

然后直接判断是否同时存在 iii×xi\times x 即可。

xnx\ge\sqrt{n} 的时候我们的复杂度是对的。

那么 x<nx<\sqrt{n} 的时候我们就换一种方法吧。

我们枚举一个 x[1,100000]x\in[1,\sqrt{100000}]

然后维护两个数组 premxp

xx 的枚举里面我们再枚举一个 i[1,n]i\in[1,n]

然后 pre[i] 表示 aia_{i} 的上一次出现位置。

mxp[i] 扫描到 ii 的时候出现了满足 a÷b=xa\div b=x 的最右位置。

维护的具体方法看注释吧。

这道题就完了。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <bitset>
#include <queue>

using namespace std;

const int Maxn = 1e5 + 5, Maxs = 400 + 5, Maxv = 310;
int n, m, each, cube, isa[Maxn], cnt[Maxn], ans[Maxn], pre[Maxn], mxp[Maxn], bel[Maxn], lps[Maxs], rps[Maxs];
struct Query
{
	int t, l, r, x, id;
	Query() {}
	Query(int t1, int t2, int t3, int t4, int t5)
	{
		t = t1;
		l = t2;
		r = t3;
		x = t4;
		id = t5;
	}
};
struct Special
{
	int l, r, id;
	Special() {}
	Special(int t1, int t2, int t3)
	{
		l = t1;
		r = t2;
		id = t3;
	}
};
vector < Query > Mq; // Mo's Algorithm | Query
vector < Special > Vq[Maxn]; // Violence | Query
bitset < Maxn > cla, inv; // classic | inverse

bool cmp(const Query &one, const Query &ano)
{
	if (bel[one.l] != bel[ano.l])   return one.l < ano.l;
	else if (bel[one.l] & 1)    return one.r < ano.r;
	else    return one.r > ano.r;
}

void Plus_Cont(int x)
{
	x = isa[x];
	if (cnt[x] == 0)
	{
		cla[x] = 1;
		inv[100000 - x] = 1;
	}
	++cnt[x];
}

void Mins_Cont(int x)
{
	x = isa[x];
	--cnt[x];
	if (cnt[x] == 0)
	{
		cla[x] = 0;
		inv[100000 - x] = 0;
	}
}

void Pare_v1()
{
	int l = 1, r = 0;
	for (auto it : Mq)
	{
		while (l > it.l)	Plus_Cont(--l);
		while (l < it.l)	Mins_Cont(l++);
		while (r > it.r)	Mins_Cont(r--);
		while (r < it.r)	Plus_Cont(++r);
		if (it.t == 1)  ans[it.id] = ((cla << it.x) & cla).any();
		else if (it.t == 2)  ans[it.id] = ((cla << (100000 - it.x)) & inv).any();
		else if (it.t == 3)
		{
			bool flag = 0;
			for (int i = 1; i * i <= it.x; ++i)
			{
				if (it.x % i == 0 && cla.test(i) && cla.test(it.x / i))
				{
					ans[it.id] = 1;
					flag = 1;
					break;
				}
			}
			if (flag == 0)  ans[it.id] = 0;
		}
		else
		{
			bool flag = 0;
			for (int i = 1; i * it.x <= 100000; ++i)
			{
				if (cla.test(i) && cla.test(i * it.x))
				{
					ans[it.id] = 1;
					flag = 1;
					break;
				}
			}
			if (flag == 0)	ans[it.id] = 0;
		}
	}
}

void Pare_v2()
{
	for (int x = 1; x <= Maxv; ++x)
	{
		if (Vq[x].empty())	continue;
		int now = 0;
		for (int i = 1; i <= n; ++i)
		{
			int y = isa[i];
			pre[y] = i;
			if (x * y <= 100000)	now = max(now, pre[x * y]);
			if (y % x == 0) 	now = max(now, pre[y / x]);
			mxp[i] = now;
		}
		for (auto it : Vq[x])
		{
			if (it.l <= mxp[it.r])	ans[it.id] = 1;
			else	ans[it.id] = 0; 
		}
		memset(pre, 0, sizeof pre);
		memset(mxp, 0, sizeof mxp);
	}
}

char ANS[2][10] = { "yumi", "yuno" };
signed main()
{
	scanf("%d %d", &n, &m);
	each = 320;
	cube = (n - 1) / each + 1;
	for (int i = 1; i <= n; ++i)	scanf("%d", &isa[i]);
	for (int i = 1; i <= cube; ++i)
	{
		lps[i] = rps[i - 1] + 1;
		rps[i] = rps[i - 1] + each;
		if (i == cube) 	rps[i] = n;
		for (int j = lps[i]; j <= rps[i]; ++j)  bel[j] = i;
	}
	for (int i = 1, t, l, r, x; i <= m; ++i)
	{
		scanf("%d %d %d %d", &t, &l, &r, &x);
		if (t == 4 && x <= Maxv)    Vq[x].emplace_back(Special(l, r, i));
		else    Mq.emplace_back(Query(t, l, r, x, i));
	}
	sort(Mq.begin(), Mq.end(), cmp);
	Pare_v1(), Pare_v2();
	for (int i = 1; i <= m; ++i)    puts(ANS[ans[i]]);
	return 0;
}

☆ 62.P5354 [Ynoi2017]由乃的OJ

第一行两个整数 n,mn,m

第二行 nn 个整数表示序列 aa

接下来 mm 行,每行四个整数:

  • 1 l r x:把区间 [l,r][l,r] 所有大于 xx 的数减去 xx
  • 2 l r x:查询区间 [l,r][l,r] 内的 xx 的出现次数。

题目里讲得非常清楚,就是把 睡觉困难综合征 搬到树上了。

那我们先回想一下 睡觉困难综合征 是怎么做的:

首先我们设了两个值,一个在二进制下全是 11 (下文中为 mxmx ),一个全是 00 (下文中为 mnmn )。

然后我们直接对序列中每一个操作用这两个值进行模拟。

因为二进制运算每个位独立,所以我们可以得到一开始的时候每个位选 0011 时最后能够得到 00 还是 11

然后我们就贪心进行选取 0/10/1 就好了。

先考虑一个序列如何动态维护这个问题。

我们可以开一棵线段树,维护区间内一开始是 mxmxmnmn ,一直把这个区间模拟完之后的结果。

那么问题来了,如何合并两个区间的答案呢?

我们先按位分析:现在知道这位选 1100 分别在两边会得到什么结果,那我们先模拟这位选什么,用在左边的结果再拿到右边去看放进去会得到什么结果。这样就可以得到整个区间这位选 1100 的结果了。

那么我们一共枚举最多 6464 位, 6464 的常数瞬间爆炸,所以肯定不能按位枚举。

想一想如果优化这个过程。

我们可以先得到左边全选 1100 时会得到的结果上哪些位是 11 ,哪些位是 00 。然后去右边区间看下这些是 11 的位上哪些又会变成 0011 ,是 00 的位上哪些又是 1100

首先设 tmp1tmp1 表示经过左边之后是 11 的位置, tmp0tmp0 表示经过左边之后是 00 的位置。

那么 tmp1=now,tmp0=now;nownow 表示当前左边全选 0011 的结果)

然后得到答案 ans=(tmp1&right1)|(tmp0&right0)。( right0/1right0/1 表示右边全选 0/10/1 的结果)

就相当于把左边结果的 0011 分别处理,把是0011的位上的右边的结果直接搬过来。

因为这一位经过左边后变成了 0/10/1,所以这一位经过右边之后就应该是右边一开始这位选 0/10/1 的结果。

这样我们就做到了 O(1)\operatorname{O}(1) 合并两个区间的结果。

最后再用树链剖分把这个过程搬到树上就好了。

这道题的两个端点的顺序是有影响的。所以要在树剖里分类讨论,还要维护区间倒着来的结果。

细节很多WA了很久,不愧是Ynoi

代码:

#include<cstdio>
#include<vector>
using namespace std;
vector<unsigned long long> e[100010];
struct node
{
	unsigned long long one,zero;
}nodes[400010],exnodes[400010],emp,ret,exret;//nodes是正着来,exnodes是反着来 
const unsigned long long cone=1;//1常量,免得1<<i爆掉 
unsigned long long mx,mn;//全为1和全为0的量 
unsigned long long n,m,k,opx[100010],op[100010],u,v,opt,opa,opb,opc;
unsigned long long siz[100010],son[100010],dep[100010],fa[100010],hb[100010],dfn[100010],ton[100010],cnt;
bool flag,exflag;
void dfs1(unsigned long long x,unsigned long long las)
{
	fa[x]=las;
	dep[x]=dep[las]+cone;
	siz[x]=cone;
	for(unsigned long long i=0;i<e[x].size();++i)
	{
		unsigned long long y=e[x][i];
		if(y^las)
		{
			dfs1(y,x);
			siz[x]+=siz[y];
			if(siz[son[x]]<siz[y])	son[x]=y;
		}
	}
}
void dfs2(unsigned long long x,unsigned long long las,bool heavy)
{
	dfn[x]=++cnt;
	ton[cnt]=x;
	if(heavy)	hb[x]=hb[las];
	else	hb[x]=x;
	if(son[x])	dfs2(son[x],x,1);
	for(unsigned long long i=0;i<e[x].size();++i)
	{
		unsigned long long y=e[x][i];
		if((y^las)&&(y^son[x]))	dfs2(y,x,0);
	}
}
node merge(node one,node ano)
{
	node res=emp;
	unsigned long long tmp1=one.one;
	unsigned long long tmp0=~tmp1;
	res.one=(tmp1&ano.one)|(tmp0&ano.zero);
	tmp1=one.zero;
	tmp0=~tmp1;
	res.zero=(tmp1&ano.one)|(tmp0&ano.zero);//全选0/1分别维护 
	return res;
}
void build(unsigned long long l,unsigned long long r,unsigned long long x)
{
	if(l^r)
	{
		unsigned long long mid=(l+r)>>cone;
		build(l,mid,x<<cone);
		build(mid+cone,r,x<<cone|cone);
		nodes[x]=merge(nodes[x<<cone],nodes[x<<cone|cone]);
		exnodes[x]=merge(exnodes[x<<cone|cone],exnodes[x<<cone]);//反着来的肯定也要反着合并 
	}
	else
	{
		nodes[x]=exnodes[x]=emp;
		if(op[ton[l]]==cone)//初值直接模拟就好 
		{
			nodes[x].one&=opx[ton[l]];
			exnodes[x].one&=opx[ton[l]];
			nodes[x].zero&=opx[ton[l]];
			exnodes[x].zero&=opx[ton[l]];
		}
		else if(op[ton[l]]==2)
		{
			nodes[x].one|=opx[ton[l]];
			exnodes[x].one|=opx[ton[l]];
			nodes[x].zero|=opx[ton[l]];
			exnodes[x].zero|=opx[ton[l]];
		}
		else
		{
			nodes[x].one^=opx[ton[l]];
			exnodes[x].one^=opx[ton[l]];
			nodes[x].zero^=opx[ton[l]];
			exnodes[x].zero^=opx[ton[l]];
		}
	}
}
void update(unsigned long long l,unsigned long long r,unsigned long long x,unsigned long long pos,unsigned long long oper,unsigned long long val)
{
	if(l^r)
	{
		unsigned long long mid=(l+r)>>cone;
		if(pos<=mid)	update(l,mid,x<<cone,pos,oper,val);
		else	update(mid+cone,r,x<<cone|cone,pos,oper,val);
		nodes[x]=merge(nodes[x<<cone],nodes[x<<cone|cone]);
		exnodes[x]=merge(exnodes[x<<cone|cone],exnodes[x<<cone]);
	}
	else
	{
		op[ton[l]]=oper;
		opx[ton[l]]=val;
		nodes[x]=exnodes[x]=emp;
		if(op[ton[l]]==cone)
		{
			nodes[x].one&=opx[ton[l]];
			exnodes[x].one&=opx[ton[l]];
			nodes[x].zero&=opx[ton[l]];
			exnodes[x].zero&=opx[ton[l]];
		}
		else if(op[ton[l]]==2)
		{
			nodes[x].one|=opx[ton[l]];
			exnodes[x].one|=opx[ton[l]];
			nodes[x].zero|=opx[ton[l]];
			exnodes[x].zero|=opx[ton[l]];
		}
		else
		{
			nodes[x].one^=opx[ton[l]];
			exnodes[x].one^=opx[ton[l]];
			nodes[x].zero^=opx[ton[l]];
			exnodes[x].zero^=opx[ton[l]];
		}
	}
}
void find(unsigned long long l,unsigned long long r,unsigned long long x,unsigned long long fr,unsigned long long ba)//y用的find 
{
	if(l>ba||r<fr)	return;
	if(l>=fr&&r<=ba)
	{
		if(!flag)
		{
			ret=nodes[x];
			flag=cone;
		}
		else	ret=merge(nodes[x],ret);//LCA到y中是dfs序从小到大,y的dfs序大,所以要放到后面,并和正序的结果合并
	}
	else
	{
		unsigned long long mid=(l+r)>>cone;
		find(mid+cone,r,x<<cone|cone,fr,ba);//y放到后面 
		find(l,mid,x<<cone,fr,ba);
	}
}
void exfind(unsigned long long l,unsigned long long r,unsigned long long x,unsigned long long fr,unsigned long long ba)//x用的exfind 
{
	if(l>ba||r<fr)	return;
	if(l>=fr&&r<=ba)
	{
		if(!exflag)
		{
			exret=exnodes[x];
			exflag=cone;
		}
		else	exret=merge(exret,exnodes[x]);//x到LCA中是dfs序从大到小,x的dfs序大,所以要放到后面,并和倒序的结果合并
	}
	else
	{
		unsigned long long mid=(l+r)>>cone;
		exfind(mid+cone,r,x<<cone|cone,fr,ba);//x放到后面 
		exfind(l,mid,x<<cone,fr,ba);
	}
}
node LCA(unsigned long long x,unsigned long long y)
{
	unsigned long long fx=hb[x],fy=hb[y];
	flag=exflag=0;//如果之前没有值,就直接把值赋给ret/exret 
	ret=exret=emp;
	while(fx^fy)
	{
		if(dep[fx]>dep[fy])//分类讨论 
		{
			exfind(cone,n,cone,dfn[fx],dfn[x]);//x这边是从x到LCA往上,但x的dfs序更大 
			x=fa[fx];
			fx=hb[x];
		}
		else
		{
			find(cone,n,cone,dfn[fy],dfn[y]);//y这边是从LCA到y往下,但y的dfs序更大
			y=fa[fy];
			fy=hb[y];
		}
	}
	if(dep[x]<dep[y])	find(cone,n,cone,dfn[x],dfn[y]);
	else	exfind(cone,n,cone,dfn[y],dfn[x]);
	return merge(exret,ret);//x这边在前面 
}
unsigned long long solve(node how,unsigned long long lim)//贪心选取 
{
	unsigned long long res=0,used=0;
	for(unsigned long long i=k-1;i>=0;--i)
	{
		if(how.zero&(cone<<i))	res+=(cone<<i);
		else if((how.one&(cone<<i))&&used+(cone<<i)<=lim)
		{
			res+=(cone<<i);
			used+=(cone<<i);
		}
		if(i==0)	break;
	}
	return res;
}
int main()
{
	scanf("%llu %llu %llu",&n,&m,&k);
	mx=-cone;
	mn=0;
	emp.one=mx;
	emp.zero=mn;
	for(unsigned long long i=cone;i<=n;++i)	scanf("%llu %llu",&op[i],&opx[i]);
	for(unsigned long long i=cone;i<n;++i)
	{
		scanf("%llu %llu",&u,&v);
		e[u].push_back(v);
		e[v].push_back(u);
	} 
	dfs1(cone,cone);
	dfs2(cone,cone,0);
	build(cone,n,cone);
	for(unsigned long long i=cone;i<=m;++i)
	{
		scanf("%llu %llu %llu %llu",&opt,&opa,&opb,&opc);
		if(opt==2)	update(cone,n,cone,dfn[opa],opb,opc);
		else	printf("%llu\n",k?solve(LCA(opa,opb),opc):0);
	}
	return 0;
}

☆ 63.P5048 [Ynoi2019模拟赛]Yuno loves sqrt technology III

给你一个长为 nn 的序列 aamm 次询问,每次查询一个区间的众数的出现次数,强制在线。


§ 题意简述

区间众数出现次数强制在线。

§ 题解

三个 YLST 中比较清新的一个分块。

比较重点的地方在于询问散块的处理。

先离散化一下序列。

我们首先预处理出来一个 vector 数组 fur[i]fur[i] 里面依次存的是所有 isa[i](即这个序列,详见代码)的出现位置,再预处理一个 pos[i] 表示在当前第 ii 位时 fur[i] 的大小也就是一共出现了多少个 isa[i]。由于 vector 的下标是从 00 开始的,所以所有的 pos[i] 都需要减个一。

然后询问处理整块的时候,我们先假设当前询问的区间是 [opl,opr],然后把当前询问的答案 res 先置为 App[bel[opl] + 1][bel[opr] - 1]

然后来考虑散块,在处理出的 vector 数组中判断即可。

设散块处理到数 isa[i],那么如果存在 pos[i] + res <= fur[isa[i]].size() - 1fur[isa[i]][pos[i] + res] <= opr,那么则说明这个数出现了至少 res + 1 次,将 res 加一即可。

由于 res 最多加不超过 Θ(2n)\Theta(2\sqrt{n}) 次,所以复杂度是对的。

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>

using namespace std;

const int MAXN = 5e5 + 5, MAXM = 720 + 5;

char buf[1 << 21], *p1 = buf, *p2 = buf;
#define getchar( ) ( p1 == p2 && ( p2 = ( p1 = buf ) + fread( buf, 1, 1 << 21, stdin ), p1 == p2 ) ? EOF : *p1 ++ )

template<typename _T>
void read( _T &x ){
	x = 0; char c = getchar( ); _T f = 1;
	while( c < '0' || c > '9' ){ if( c == '-' )	f = -1; c = getchar( ); }
	while( c >= '0' && c <= '9' ){ x = ( x << 3 ) + ( x << 1 ) + ( c & 15 ); c = getchar( ); }
	x *= f;
}

template<typename _T>
void write( _T x ){
	if( x < 0 ){ putchar( '-' ); x = -x; }
	if( x > 9 ){ write( x / 10 ); }
	putchar( x % 10 + '0' );
}

template<typename _T>
void swapp( _T &one, _T &another ){ int temp = one; one = another; another = temp; }

template<typename _T>
_T MIN( _T one, _T another ){ return one > another ? another : one; }

template<typename _T>
_T MAX( _T one, _T another ){ return one > another ? one : another; }

int N, M;
int cube, each, kase, isa[MAXN], cnt[MAXN], pos[MAXN], vis[MAXN], bel[MAXN];
int lps[MAXM], rps[MAXM], app[MAXM], App[MAXM][MAXM];
vector<int> disc, fur[MAXN];

int getID( int x ){ return lower_bound( disc.begin( ), disc.end( ), x ) - disc.begin( ) + 1; }

void build( ){
	for( int i = 1; i <= cube; ++ i ){
		kase ++;
		for( int j = lps[i]; j <= rps[i]; ++ j ){
			if( vis[isa[j]] != kase )	cnt[isa[j]] = 0;
			cnt[isa[j]] ++; app[i] = MAX( app[i], cnt[isa[j]] );
			vis[isa[j]] = kase;
		}
	}
	memset( cnt, 0, sizeof( cnt ) );
	for( int i = 1; i <= cube; ++ i ){
		kase ++;
		for( int j = i; j <= cube; ++ j ){
			App[i][j] = App[i][j - 1];
			for( int k = lps[j]; k <= rps[j]; ++ k ){
				if( vis[isa[k]] != kase )	cnt[isa[k]] = 0;
				cnt[isa[k]] ++; App[i][j] = MAX( App[i][j], cnt[isa[k]] );
				vis[isa[k]] = kase;
			}
		}
	}
	memset( cnt, 0, sizeof( cnt ) );
}

int query( int opl, int opr ){
	if( bel[opl] == bel[opr] ){
		int res = 0; kase ++;
		for( int i = opl; i <= opr; ++ i ){
			if( vis[isa[i]] != kase )	cnt[isa[i]] = 0;
			cnt[isa[i]] ++; res = MAX( res, cnt[isa[i]] );
			vis[isa[i]] = kase;
		}
		return res;
	}
	int res = 0;
//	res = App[bel[opl] + 1][bel[opr] - 1];
	for( int i = bel[opl] + 1; i < bel[opr]; ++ i )	res += app[i];
//	for( int i = bel[opl] + 1; i < bel[opr]; ++ i )	res += App[i][i];
	for( int i = opl; i <= rps[bel[opl]]; ++ i ){
		int lim = fur[isa[i]].size( ) - 1;
		while( pos[i] + res <= lim && fur[isa[i]][pos[i] + res] <= opr )	res ++;
	}
	for( int i = lps[bel[opr]]; i <= opr; ++ i ){
		while( pos[i] - res >= 0 && fur[isa[i]][pos[i] - res] >= opl )	res ++;
	}
	return res;
}

signed main( ){
	read( N ); read( M ); each = 720; cube = ( N - 1 ) / each + 1;
	for( int i = 1; i <= N; ++ i ){ read( isa[i] ); disc.push_back( isa[i] ); }
	sort( disc.begin( ), disc.end( ) );
	disc.erase( unique( disc.begin( ), disc.end( ) ), disc.end( ) );
	for( int i = 1; i <= N; ++ i ){
		isa[i] = getID( isa[i] );
		fur[isa[i]].push_back( i );
		pos[i] = fur[isa[i]].size( ) - 1;
	}
	for( int i = 1; i <= cube; ++ i ){
		lps[i] = rps[i - 1] + 1; rps[i] = rps[i - 1] + each;
		if( i == cube )	rps[i] = N;
		for( int j = lps[i]; j <= rps[i]; ++ j )	bel[j] = i;
	}
	build( );
	int Ans = 0, opl, opr;
	while( M -- > 0 ){
		read( opl ); read( opr ); opl ^= Ans; opr ^= Ans;
		Ans = 0; if( opl > opr )	swapp( opl, opr );
		write( Ans = query( opl, opr ) ); putchar( '\n' );
	}
	return 0;
}

☆ 64.「LibreOJ β Round #4」框架

没想到吧,我他娘的更新了

看到这道题

WGY说:bitset优化轻松过掉

LJS说:bitset优化裸题

ljs说:暴力打表能过!

那么这里是数据结构一百题,怎么能用这些方法呢?

所以我们来看看这道题吧:

有一个 n×mn\times m的矩形框架,但其中有些边被删除了。qmqmqm\operatorname{qmqmqm} 想知道剩余部分中还有多少完整的正方形。

2n,m1032\leq n,m\leq 10^3

先不看数据范围

考虑最朴素的暴力

三重循环分别枚举:左上角横坐标,左上角纵坐标,正方形边长

时间复杂度n3n^3

妥妥超时

那我们怎么优化呢

开一个数组记录一个点往左和往上最多能往上走几条边,由于是正方形,取这两个值中较小的一个就行了。

那么我们可以知道以每个点为右下角时正方形的边长最大值。

只需要再判断左上角是否能够走那么长了。

我们发现,以一个点为正方形的右下角时,可能的左上角位置在一条直线上,那么我们可以直接处理这一条直线。

每个点有往左上可以走到的最大距离,我们可以求出每个点往右下走的最大距离。如果两个点可以互相到达,那么它们就可以作为一个正方形的两个顶点。

我们可以用主席树解决上面的问题:版本 ii 记录第 11ii 的点往前最多可以到达哪个点。

我们只在最远点上加个 11。因为之后查询会考虑到的。

再枚举每个点,查询从它后面到它往后最多能跳到的点中有多少点能够跳到它。

也就是这个区间的点中有多少个点能跳到的最远点小于等于它。

为什么有小于?因为如果能跳到更前面,那肯定能跳到这里。这就是之前只在最远点上加 11 的原因。(其实区间加单点查也是可以的)

对于前面记录最多能跳到哪里的数组,我们用二分来获取它。

总时间复杂度:

二分:n2lognn^2logn(每个点一次二分)

主席树:n2lognn^2logn(主席树对每个点操作一次)

总:n2lognn^2logn(因为二分和主席树是两个分开的部分)

跑不过 bitsetbitset 嘤嘤嘤...

代码:(注意细节)

#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,lsum[1010][1010],hsum[1010][1010],dp[1010][1010],root[1010],ans,tot;
struct node
{
	int l,r,num;
}nodes[100010];
int search(int l,int r,int x,int y)
{
	while(l+1<r)
	{
		int mid=(l+r)>>1;
		if(lsum[x][y]-lsum[x-mid][y]==mid&&hsum[x][y]-hsum[x][y-mid]==mid)	l=mid;
		else	r=mid;
	}
	return l;
}
int exsearch(int l,int r,int x,int y)
{
	while(l+1<r)
	{
		int mid=(l+r)>>1;
		if(lsum[x+mid][y]-lsum[x][y]==mid&&hsum[x][y+mid]-hsum[x][y]==mid)	l=mid;
		else	r=mid;
	}
	return l;
}
void ins(int l,int r,int pre,int &now,int pos)
{
	now=++tot;
	nodes[now]=nodes[pre];
	++nodes[now].num;
	if(l^r)
	{
		int mid=(l+r)>>1;
		if(pos<=mid)	ins(l,mid,nodes[pre].l,nodes[now].l,pos);
		else	ins(mid+1,r,nodes[pre].r,nodes[now].r,pos);
	}
}
int find(int l,int r,int v1,int v2,int fr,int ba)
{
	if(l>ba||r<fr)	return 0;
	if(l>=fr&&r<=ba)	return nodes[v2].num-nodes[v1].num;
	int mid=(l+r)>>1;
	return find(l,mid,nodes[v1].l,nodes[v2].l,fr,ba)+find(mid+1,r,nodes[v1].r,nodes[v2].r,fr,ba);
}
void solve(int x,int y)
{
	tot=0;
	int siz=min(n-x,m-y)+1;
	for(int i=x,j=y,k=1;i<=n&&j<=m;++i,++j,++k)	ins(1,siz,root[k-1],root[k],k-dp[i][j]);
	for(int i=x,j=y,k=1;i<=n&&j<=m;++i,++j,++k)	ans+=find(1,siz,root[k],root[k+exsearch(0,min(n-i,m-j)+1,i,j)],1,k);
}
int main()
{
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;++i)
	{
		for(int j=2;j<=m;++j)
		{
			scanf("%d",&hsum[i][j]);
			hsum[i][j]+=hsum[i][j-1];
		}
	}
	for(int i=2;i<=n;++i)
	{
		for(int j=1;j<=m;++j)
		{
			scanf("%d",&lsum[i][j]);
			lsum[i][j]+=lsum[i-1][j];
		}
	}
	for(int i=1;i<=n;++i)
	{
		for(int j=1;j<=m;++j)
		{
			dp[i][j]=search(0,min(i,j),i,j);
		}
	}
	for(int i=1;i<=m;++i)	solve(1,i);
	for(int i=2;i<=n;++i)	solve(i,1);
	printf("%d",ans);
	return 0;
}

☆ 65. P4689 [Ynoi2016]这是我自己的发明

我首先认为这是 SNOI2017 一个简单的询问 搬到树上。

我们传统地把此题分为两个 pass\texttt{pass},一个询问,一个修改。

  • pass 1\texttt{pass 1}:询问

我直接按 一个简单的询问 的方法讲。其实是把以前的题解 copy 过来了

由于是出现次数,满足区间加减性,所以我们可以这样表达 get(l,r,x)\mathrm{get}(l,r,x)(省略 xx):

get(l,r)=get(1,r)get(1,l1)\mathrm{get}(l,r)=\mathrm{get}(1,r)-\mathrm{get}(1,l-1)

那么我们代进原式,化一波式子(get(p)=get(1,p,x)\mathrm{get}(p)=\mathrm{get}(1,p,x)):

i=1get(l1,r1,x)×get(l2,r2,x)\sum_{i=1}^{\infty}\mathrm{get}(l_{1},r_{1},x)\times\mathrm{get}(l_{2},r_{2},x) i=1(get(1,r1)get(1,l11))×(get(1,r2)get(1,l21))\sum_{i=1}^{\infty}(\mathrm{get}(1,r_{1})-\mathrm{get}(1,l_{1}-1))\times(\mathrm{get}(1,r_{2})-\mathrm{get}(1,l_{2}-1)) i=1get(r1)×get(r2)get(r1)×get(l21)get(l11)×get(r2)+get(l11))×get(l21)\sum_{i=1}^{\infty}\mathrm{get}(r_{1})\times\mathrm{get}(r_{2})-\mathrm{get}(r_{1})\times\mathrm{get}(l_{2}-1)-\mathrm{get}(l_{1}-1)\times\mathrm{get}(r_{2})+\mathrm{get}(l_{1}-1))\times\mathrm{get}(l_{2}-1) let F(x,y)=dget(1,l,d)×get(1,r,d)\mathrm{let}\ F(x,y)=\sum_{d}\mathrm{get}(1,l,d)\times\mathrm{get}(1,r,d)

则答案为:

F(r1,r2)F(r1,l21)F(l11,r2)+F(l11,l21)F(r_{1},r_{2})-F(r_{1},l_{2}-1)-F(l_{1}-1,r_{2})+F(l_{1}-1,l_{2}-1)

考虑怎么更新,比如从 ll 更新到 l+1l+1,则:

get(1,l)×get(1,r)\mathrm{get(1,l)}\times\mathrm{get}(1,r) get(1,l+1)×get(1,r)\mathrm{get(1,l+1)}\times\mathrm{get}(1,r) get(1,l)×get(1,r)+cont(al)\mathrm{get(1,l)}\times\mathrm{get}(1,r)+\mathrm{cont}(a_{l})

其中 cont(al)\mathrm{cont}(a_{l}) 表示 ala_{l} 的出现次数。

则我们就知道怎么更新了,由于我们维护和的是前缀信息,所以姿势和普通莫队有点不一样。

维护两个数组 cntl[x]cntr[y] 表示答案式子

F(r1,r2)F(r1,l21)F(l11,r2)+F(l11,l21)F(r_{1},r_{2})-F(r_{1},l_{2}-1)-F(l_{1}-1,r_{2})+F(l_{1}-1,l_{2}-1)

子树的话直接 DFS 序拍到序列上。

  • pass 2\texttt{pass 2}:修改

现在我们面临着查询操作我们是用莫队整的,但这个修改貌似不单纯。其实也是从树剖模板缝合过来的

分类讨论,设我们当前要换的根为 rtrt,现在来处理询问,设查询的节点为 uuLCA(u,v)\text{LCA}(u,v) 为节点 uu 和节点 vv 的最近公共祖先。

    • 如果 rt=urt=u,则我们直接对整棵树进行查询。
    • 如果 LCA(u,rt)u\text{LCA}(u,rt)\neq u,此时修改不影响查询。
    • 如果 LCA(u,rt)=u\text{LCA}(u,rt)=u,此时 rtrtuu 的子树里,那么需要查询的地方就很明确了,后面的步骤显然。

于是我们不需要实际的去处理这个修改,然后就可以直接莫队了。

(整体感觉是个 原题+假上树+树剖模板 的缝合题)

/* Clearink */

#include <cstdio>
#include <algorithm>

using namespace std;
typedef long long LL;

const int MAXN = 5e5 + 5, MAXM = 1e6 + 5;

int rint () {
	int x = 0, f = 1; char c = getchar ();
	for ( ; c < '0' || c > '9'; c = getchar () )	f = c == '-' ? -1 : f;
	for ( ; c >= '0' && c <= '9'; c = getchar () )	x = ( x << 3 ) + ( x << 1 ) + ( c & 15 );
	return x * f;
}

template<class _T>
void wint ( _T x ) {
	if ( x < 0 )	putchar ( '-' ), x = ~ x + 1;
	if ( x > 9 )	wint ( x / 10 );
	putchar ( x % 10 ^ '0' );
}

template<class _T> void swapp ( _T& x, _T& y ) { _T w = x; x = y; y = w; }

struct GraphSet {
	int to, nx;
	GraphSet () : to ( 0 ), nx ( 0 ) {}
	GraphSet ( const int a, const int b ) : to ( a ), nx ( b ) {}
} asg[MAXN * 2];

struct Quest {
	int l, r, ID, x;
	Quest () : l ( 0 ), r ( 0 ), ID ( 0 ), x ( 0 ) {}
	Quest ( const int a, const int b, const int c, const int d ) : l ( a ), r ( b ), ID ( c ), x ( d ) {}
} asq[MAXM * 8], itls[MAXN];

LL cur = 0, ans[MAXM], buc1[MAXN], buc2[MAXN];
int rt, pos[MAXN], blo = 320, col[MAXN], freq;
int n, m, bgn[MAXN], cnt, sjc, segl[MAXN], segr[MAXN], kfa[MAXN][21], a[MAXN], dept[MAXN], pri[MAXN], len;

void addE ( const int u, const int v ) { asg[++ cnt] = GraphSet ( v, bgn[u] ), bgn[u] = cnt; }
bool existcmp ( const Quest& one, const Quest& ano ) { return pos[one.l] == pos[ano.l] ? one.r < ano.r : one.l < ano.l; }

void dfs ( const int u, const int lst ) {
	kfa[u][0] = lst, dept[u] = dept[lst] + 1;
	segl[u] = ++ sjc, col[sjc] = a[u];
	for ( int i = 1; i <= 20; ++ i )	kfa[u][i] = kfa[kfa[u][i - 1]][i - 1];
	for ( int i = bgn[u]; i; i = asg[i].nx ) {
		int v = asg[i].to;
		if ( v == lst )	continue;
		dfs ( v, u );
	}
	segr[u] = sjc;
}

int calcKAC ( int u, int k ) {
	for ( int i = 20; ~ i; -- i ) {
		if ( k >= ( 1 << i ) )	k -= ( 1 << i ), u = kfa[u][i];
	}
	return u;
}

int calcLCA ( int u, int v ) {
	if ( dept[u] < dept[v] )	swapp ( u, v );
	for ( int i = 20; ~ i; -- i ) {
		if ( dept[kfa[u][i]] >= dept[v] )	u = kfa[u][i];
	}
	if ( u == v )	return u;
	for ( int i = 20; ~ i; -- i ) {
		if ( kfa[u][i] != kfa[v][i] )	u = kfa[u][i], v = kfa[v][i];
	}
	return kfa[u][0];
}

void initial () {
	for ( int i = 1; i <= n; ++ i )	pos[i] = ( i - 1 ) / blo + 1;
	sort ( pri + 1, pri + 1 + n );
	len = unique ( pri + 1, pri + 1 + n ) - pri - 1;
	for ( int i = 1; i <= n; ++ i )	a[i] = lower_bound ( pri + 1, pri + 1 + len, a[i] ) - pri;
	dfs ( 1, 0 );
}

void splitASdrug ( const int u, int& ils ) {
	if ( u == rt )	itls[++ ils] = Quest ( 1, n, 0, 0 );
	else {
		int lca = calcLCA ( u, rt );
		if ( lca != u )	itls[++ ils] = Quest ( segl[u], segr[u], 0, 0 );
		else {
			int ar = calcKAC ( rt, dept[rt] - dept[u] - 1 );
			if ( 1 <= segl[ar] - 1 )	itls[++ ils] = Quest ( 1, segl[ar] - 1, 0, 0 );
			if ( segr[ar] + 1 <= n )	itls[++ ils] = Quest ( segr[ar] + 1, n, 0, 0 );
		}
	}
}

void transASsub ( const int l1, const int r1, const int l2, const int r2, const int ID ) {
	asq[++ m] = Quest ( r1, r2, ID, 1 ), asq[++ m] = Quest ( r1, l2 - 1, ID, -1 );
	asq[++ m] = Quest ( l1 - 1, r2, ID, -1 ), asq[++ m] = Quest ( l1 - 1, l2 - 1, ID, 1 );
}

void transASmany ( const int l, const int r ) {
	++ freq;
	int ils = 0; splitASdrug ( l, ils );
	int aim = ils; splitASdrug ( r, ils );
	for ( int i = 1; i <= aim; ++ i ) {
		for ( int j = aim + 1; j <= ils; ++ j )	transASsub ( itls[i].l, itls[i].r, itls[j].l, itls[j].r, freq );
	}
}

void add1 ( const int x ) { cur += buc2[col[x]], buc1[col[x]] ++; }
void add2 ( const int x ) { cur += buc1[col[x]], buc2[col[x]] ++; }
void sub1 ( const int x ) { cur -= buc2[col[x]], buc1[col[x]] --; }
void sub2 ( const int x ) { cur -= buc1[col[x]], buc2[col[x]] --; }
void captainMO () {
	int nowl = 0, nowr = 0;
	for ( int i = 1; i <= m; ++ i ) {
		for ( ; nowl < asq[i].l; add1 ( ++ nowl ) ) ;
		for ( ; nowr < asq[i].r; add2 ( ++ nowr ) ) ;
		for ( ; nowl > asq[i].l; sub1 ( nowl -- ) ) ;
		for ( ; nowr > asq[i].r; sub2 ( nowr -- ) ) ;
		ans[asq[i].ID] += cur * asq[i].x;
	}
}

int main () {
	n = rint (); int _waste_ = rint ();
	for ( int i = 1; i <= n; ++ i )	a[i] = pri[i] = rint ();
	for ( int i = 1; i < n; ++ i ) {
		int u = rint (), v = rint ();
		addE ( u, v ), addE ( v, u );
	}
	initial (), rt = 1;
	for ( int i = 1; i <= _waste_; ++ i ) {
		int c = rint (), x, y;
		if ( c == 1 )	rt = rint ();
		else	x = rint (), y = rint (), transASmany ( x, y );
	}
	sort ( asq + 1, asq + 1 + m, existcmp ), captainMO ();
	for ( int i = 1; i <= freq; ++ i )	wint ( ans[i] ), putchar ( '\n' );
	return 0;
}

☆ 66. CF1491H Yuezheng Ling and Dynamic Tree

所以 Chinese Round 出 DS 是传统了对吧。

§ Description

Link.

Given is a rooted tree with the 1\sf1-th node as the root.

The tree will be given in this way: it will tell you that the parent of the i\sf i-th node is aia_{i}.

Supporting the following operations:

  • 1 l r x: let i[l,r],ai=max{aix,1}\sf \forall i\in[l,r],a_{i}=max\{a_{i}-x,1\}.
  • 2 u v: find the LCA (Lowest Common Ancestor) of u\sf u and v\sf v.

§ Solution

经典永流传。

考虑到修改操作是对结点进行的操作,然后这个东西不太能直接 LCT 或树剖,考虑照序列来分块,那么我们来对结点编号分块。

  1. 修改;

\quad维护一个属性 topu\sf top_{u} 表示在原树上结点 u\sf u 的祖先中不和 u\sf u 在同一个块里面的编号最大的一个结点的编号,如果不存在的话就令 topu=1\sf top_{u}=1。这样的话你从结点 u\sf u 跳到 root 的复杂度为 O(n)\sf O(\sqrt{n})。接下来考虑怎么维护这个东西。

\quad散块我们直接暴力扫着改;对于整块,可以发现如果一个块的被修改次数超过了块的大小,那么就一定会有 topu=fau\sf top_{u}=fa_{u}

  1. 询问。

\quad分三个类讨论,这个比较好做(差不多和树剖找 LCA 一个样子)。

#include<bits/stdc++.h>
using namespace std;
int n,m,top[100010],deln[320],tag[320],belong[100010],bl[320],br[320],fa[100010],bs;
#define gtlf(x) ((x-1)*bs+1)
#define gtrg(x) (min(x*bs,n))
void updtop(int x)
{
	if(belong[x]^belong[fa[x]])	top[x]=fa[x];
	else	top[x]=top[fa[x]];
}
void turndown(int x)
{
	if(tag[x])
	{
		for(int i=gtlf(x);i<=gtrg(x);++i)	fa[i]=max(fa[i]-tag[x],1);
		tag[x]=0;
	}
}
int main()
{
	scanf("%d %d",&n,&m),bs=sqrt(double(n))+1,fa[1]=belong[1]=1;
	for(int i=2;i<=n;++i)	scanf("%d",&fa[i]);
	for(int i=2;i<=n;++i)	belong[i]=(i-1)/bs+1,updtop(i);
	while(m--)
	{
		int opt; scanf("%d",&opt);
		if(opt==1)
		{
			int opl,opr,opx;
			scanf("%d %d %d",&opl,&opr,&opx);
			turndown(belong[opl]);
			if(belong[opl]==belong[opr])
			{
				turndown(belong[opl]);
				for(int i=opl;i<=opr;++i)	fa[i]=max(fa[i]-opx,1),updtop(i);
				for(int i=opr+1;i<=gtrg(belong[opl]);++i)	updtop(i);
			}
			else
			{
				turndown(belong[opl]);
				for(int i=opl;i<=gtrg(belong[opl]);++i)	fa[i]=max(fa[i]-opx,1),updtop(i);
				for(int i=gtlf(belong[opl]);i<opl;++i)	updtop(i);
				turndown(belong[opr]);
				for(int i=gtlf(belong[opr]);i<=opr;++i)	fa[i]=max(fa[i]-opx,1),updtop(i);
				for(int i=opr+1;i<=gtrg(belong[opr]);++i)	updtop(i);
				for(int i=belong[opl]+1;i<belong[opr];++i)
				{
					if(deln[i]>=bs)	tag[i]+=opx;
					else
					{
						++deln[i];
						for(int j=gtlf(i);j<=gtrg(i);++j)	fa[j]=max(fa[j]-opx,1),updtop(j);
					}
				}
			}
		}
		else
		{
			int opx,opy; scanf("%d %d",&opx,&opy);
			while(opx^opy)
			{
				int fopx,fopy;
				if(deln[belong[opx]]>=bs)	turndown(belong[opx]),fopx=fa[opx];
				else	fopx=top[opx];
				if(deln[belong[opy]]>=bs)	turndown(belong[opy]),fopy=fa[opy];
				else	fopy=top[opy];
				if(belong[opx]^belong[opy])
				{
					if(belong[opx]>belong[opy])	opx=fopx;
					else	opy=fopy;
				}
				else if(fopx^fopy)	opx=fopx,opy=fopy;
				else
				{
					if(opx>opy)	turndown(belong[opx]),opx=fa[opx];
					else	turndown(belong[opy]),opy=fa[opy];
				}
			}
			printf("%d\n",opx);
		}
	}
	return 0;
}

☆ 67. 「NOI2020」时代的眼泪

小 L 喜欢与智者交流讨论,而智者也经常为小 L 出些思考题。

这天智者又为小 L 构思了一个问题。智者首先将时空抽象为了一个二维平面,进而将一个事件抽象为该平面上的一个点,将一个时代抽象为该平面上的一个矩形。

为了方便,下面记 (a,b)(c,d)(a, b) \leq (c, d) 表示平面上两个点 (a,b),(c,d)(a, b),(c, d) 满足 ac,bda \leq c, b \leq d

更具体地,智者给定了 nn事件,他们用平面上 nn 个不同的点 {(xi,yi)}i=1n\{(x_i, y_i)\}^n_{i=1} 来表示;智者还给定了 mm时代,每个时代用平面上一个矩形 (ri,1,ri,2,ci,1,ci,2)(r_{i,1}, r_{i,2}, c_{i,1}, c_{i,2}) 来表示,其中 (ri,1,ci,1)(r_{i,1}, c_{i,1}) 是矩形的左下角,(ri,2,ci,2)(r_{i,2}, c_{i,2}) 是矩形的右上角,保证 (ri,1,ci,1)(ri,2,ci,2)(r_{i,1}, c_{i,1}) \leq (r_{i,2}, c_{i,2})。我们称时代 ii 包含了事件 jj 当且仅当 (ri,1,ci,1)(xj,yj)(ri,2,ci,2)(r_{i,1}, c_{i,1}) \leq (x_j, y_j ) \leq (r_{i,2}, c_{i,2})

智者认为若两个事件 i,ji, j 满足 (xi,yi)(xj,yj)(x_i, y_i) \leq (x_j, y_j),则这两个事件形成了一次遗憾。而对一个时代内包含的所有事件,它们所形成的遗憾被称为这个时代的眼泪,而形成的遗憾次数则称为该时代的眼泪的大小。现在智者想要小 L 计算每个时代的眼泪的大小

小 L 明白,如果他回答不了这个问题,他也将成为时代的眼泪,请你帮帮他。


膜拜 dX。

基本是把 dX 的题解贺了过来所以没啥参考的价值。

不过有很多细节的处理不一样,大概能算个 150\frac{1}{50} 成新?

对序列分块,把贡献分成 整块 - 散块 / 整块 - 整块/ 散块 - 整块 / 散块 - 散块 以及 散块内部 / 整块内部 共六种贡献。

ans0(l,r,x,y)\textit{ans}_{0}(l,r,x,y) 为询问 l,r,x,yl,r,x,y 的答案。

同时预处理出 lb(i,j),ub(i,j)\textit{lb}(i,j),\textit{ub}(i,j) 分别表示在块 ii 中数 jjstd::lower_bound / std::upper_bound 值,下文如果写成单元函数的形式那么就是省去了第一维。

以及预先做一个块内排序,记为 ord(i,j)\textit{ord}(i,j),表示块 ii 中排序后的第 jj 个元素。

注意本文在每个 subtask 定义的东西在其他 subtask 依然适用

  • 散块 - 散块;

两边的都是 n\sqrt{n} 级别,拉出来分别排序后归并计算顺序对即可。

  • 散块内部

考虑如何对 ans0(l,r,x,y)\textit{ans}_{0}(l,r,x,y) 进行容斥。

主要矛盾集中在:会出现 (a[1,x),b[x,y])(a\in[1,x),b\in[x,y]) 这样的贡献。令 cnt0(i,j)\textit{cnt}_{0}(i,j) 表示 [lp,i][\textit{lp},i]rank1\textit{rank}_{1} 小于 jj 的数的数量,其中 lp\textit{lp} 是当前块的左端点,下同,如果出现 rp\textit{rp} 同理,rank1\textit{rank}_{1} 的定义见下文。

则容斥可以写为 ans0(l,r,x,y)=ans0(l,r,1,y)ans0(l,r,1,x1)i=lr[ai[x,y]]cnt0(i,lb(x)1)\textit{ans}_{0}(l,r,x,y)=\textit{ans}_{0}(l,r,1,y)-\textit{ans}_{0}(l,r,1,x-1)-\sum_{i=l}^{r}[a_{i}\in[x,y]]\cdot\textit{cnt}_{0}(i,\textit{lb}(x)-1)

又有 ans0(l,r,1,x)=i=lr[aix]cnt0(i,rank1(i))\textit{ans}_{0}(l,r,1,x)=\sum_{i=l}^{r}[a_{i}\leqslant x]\cdot\textit{cnt}_{0}(i,\textit{rank}_{1}(i)),我们就可以做到单次 O(n)\mathcal{O}(\sqrt{n}),注意的 l,rl,r 触及散块边界者不同时,对 cnt0\textit{cnt}_{0} 的容斥也有区别。

  • 整块 - 整块

cnt1(i,j)\textit{cnt}_{1}(i,j) 为块 [1,i][1,i]j\leqslant j 的元素个数,ans1(L,R,x,y)\textit{ans}_{1}(L,R,x,y) 为块 [L,R][L,R] 的答案,以及 rank0(i,j)\textit{rank}_{0}(i,j) 是块 ii 中排名 jj 的元素在原数组中的下标。

我们掏出传统容斥:ans1(L,R,x,y)=ans1(L,R,1,y)ans1(L,R,1,x1)i=LRPiQi\textit{ans}_{1}(L,R,x,y)=\textit{ans}_{1}(L,R,1,y)-\textit{ans}_{1}(L,R,1,x-1)-\sum_{i=L}^{R}P_{i}Q_{i}PiP_{i} 是块 [L,i)[L,i)<x<x 的元素个数,QiQ_{i} 是块 ii[x,y]\in[x,y] 的元素个数。

考虑算 ans1(L,R,1,x)\textit{ans}_{1}(L,R,1,x)

定义 rank1(i,j)\textit{rank}_{1}(i,j) 为块 ii 中第 jj 个元素的排名(从小到大,下同),rank2(i,j)\textit{rank}_{2}(i,j) 为块 ii 中满足 <j<j 的最大元素的排名,preb(i,j)\textit{pre}_{b}(i,j) 为块 [i,j][i,j] 中所有 <rank1(i,j)<\textit{rank}_{1}(i,j) 的元素数量。

易知 preb(i,j)=cnt1(i,rank1(i,j)1)\textit{pre}_{b}(i,j)=\textit{cnt}_{1}(i,\textit{rank}_{1}(i,j)-1),再定义 cp0(i,L,r)n,n,n\overset{\sqrt{n},\sqrt{n},\sqrt{n}}{\textit{cp}_{0}(i,L,r)} 为块 [1,L][1,L] 与块 iirr 小的元素组成的顺序对数量,同样易知 cp0(i,L,r)=kT[rank1(i,k)r]preb(L,rank1(i,k))\textit{cp}_{0}(i,L,r)=\sum_{k\in T}[\textit{rank}_{1}(i,k)\leqslant r]\cdot\textit{pre}_{b}(L,\textit{rank}_{1}(i,k)),其中 TT 是块 ii 的元素集。但这样搞状态数 O(nn)\mathcal{O}(n\sqrt{n}) 转移还要 n\mathcal{\sqrt{n}} 而且不好前缀和。

不过可以发现使用 ord\textit{ord} 数组 cp0\textit{cp}_{0} 就可以递推了:cp0(i,L,r)=k=lpr+lp1preb(L,k)=cp0(i,L,r1)+preb(L,r+lp1)\textit{cp}_{0}(i,L,r)=\sum_{k=lp}^{r+lp-1}\textit{pre}_{b}(L,k)=\textit{cp}_{0}(i,L,r-1)+\textit{pre}_{b}(L,r+lp-1)

然后 ans1(L,R,1,x)=i=L+1Rcp0(i,i1,rank2(i,x))cp0(i,L1,rank2(i,x))\textit{ans}_{1}(L,R,1,x)=\sum_{i=L+1}^{R}\textit{cp}_{0}(i,i-1,\textit{rank}_{2}(i,x))-\textit{cp}_{0}(i,L-1,\textit{rank}_{2}(i,x))

预处理 cp0\textit{cp}_{0}O(nn)\mathcal{O}(n\sqrt{n}),单次回答 O(n)\mathcal{O}(\sqrt{n})

  • 散块 - 整块

枚举散块里面的元素,利用 cnt1(i,j)\textit{cnt}_{1}(i,j) 计算答案。

具体是令散块元素集为 TT,整块编号为 L,RL,RiTcnt1(R,i)cnt1(L1,i)\sum_{i\in T}\textit{cnt}_{1}(R,i)-\textit{cnt}_{1}(L-1,i)

  • 整块 - 散块

和上面有什么区别吗?

  • 整块内部

预处理数组 cp1(i,x,y)n,n,n\overset{\sqrt{n},\sqrt{n},\sqrt{n}}{\textit{cp}_{1}(i,x,y)} 表示取 ord(i,xy)\textit{ord}(i,x\dots y) 组成的序列的顺序对数量。

rank0\textit{rank}_{0} 来预处理:cp1(i,x,y)=cp1(i,x,y1)+cnt0(rank0(i,y),y1)cnt0(rank0(i,y),x1)\textit{cp}_{1}(i,x,y)=\textit{cp}_{1}(i,x,y-1)+\textit{cnt}_{0}(\textit{rank}_{0}(i,y),y-1)-\textit{cnt}_{0}(\textit{rank}_{0}(i,y),x-1)


综上,这个问题得以一个 O(nn)\mathcal{O}(n\sqrt{n}) 的在线算法解决。

//almost copied from dead_X sry
//kouhu has no qiantu
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
#define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[1<<21],*p1=buf,*p2=buf;
inline int read()
{
	int x=0;char c=getchar();
	while(c<'0' || c>'9') c=getchar();
	while(c>='0' && c<='9') x=x*10+(c&15),c=getchar();
	return x;
}
const int N=101111,A=400,BS=A+10;
ll cp0[BS][BS][BS];
int a[N],rk0[BS][BS],cnt0[N][BS],cp1[BS][BS][BS],lb[BS][N],rk1[N],cnt1[BS][N],L[BS],R[BS];
bool cmp(int x,int y) { return a[x]<a[y]; }
int main(){
#ifdef ONLINE_JUDGE
	freopen("tears.in","r",stdin);
	freopen("tears.out","w",stdout);
#endif
	int n=read(),m=read(),B=n/A;
	for(int i=0;i<n;++i)a[i]=read();
	for(int i=n;i<(B+1)*A;++i)a[i]=i;
	for(int i=0;i<=B;++i){
		for(int j=i*A,k=0;k<A;++j,++k)rk0[i][k]=j;
		sort(rk0[i],rk0[i]+A,[](int x,int y){return a[x]<a[y];});
		for(int j=0;j<A;++j)rk1[rk0[i][j]]=j,cnt0[rk0[i][j]][j]=1;
		for(int j=i*A+1;j<(i+1)*A;++j)
			for(int k=0;k<A;++k)cnt0[j][k]+=cnt0[j-1][k];
		for(int j=i*A;j<(i+1)*A;++j)
			for(int k=1;k<A;++k)cnt0[j][k]+=cnt0[j][k-1];
		for(int j=i*A;j<(i+1)*A;++j)++cnt1[i][a[j]];
		if(i)for(int j=1;j<=101000;++j)cnt1[i][j]+=cnt1[i-1][j];
		for(int j=1,k=0;j<=101000;++j)(k<A)&&(j>=a[rk0[i][k]])&&(++k),lb[i][j]=k;
	}
	for(int i=0;i<=B;++i)
		for(int j=1;j<=101000;++j)cnt1[i][j]+=cnt1[i][j-1];
	for(int i=1;i<B;++i)for(int j=0;j<i;++j)for(int k=0;k<A;++k)
		cp0[i][j][k+1]=cnt1[j][a[rk0[i][k]]]+cp0[i][j][k];
	for(int i=0;i<B;++i)for(int j=0;j<A;++j)for(int k=j+1;k<A;++k)
		cp1[i][j][k]=cp1[i][j][k-1]+cnt0[rk0[i][k]][k-1]-((j==0)?0:cnt0[rk0[i][k]][j-1]);
	for(;m;--m){
		int l=read()-1,r=read()-1,x=read(),y=read(),bl=l/A,br=r/A;
		if(bl==br){
			int ans=0;
			for(int i=l;i<=r;++i){
				if(x<=a[i]&&a[i]<=y&&rk1[i])ans+=cnt0[i][rk1[i]-1]-((l%A)?cnt0[l-1][rk1[i]-1]:0);
				if(lb[bl][x-1]&&x<=a[i]&&a[i]<=y)ans-=cnt0[i][lb[bl][x-1]-1]-((l%A&&lb[bl][x-1])?cnt0[l-1][lb[bl][x-1]-1]:0);
			}
			printf("%d\n",ans);
		}
		else{
			ll ans=0;
			for(int i=l;i<(bl+1)*A;++i){
				if(x<=a[i]&&a[i]<=y&&rk1[i])ans+=cnt0[i][rk1[i]-1]-((l%A)?cnt0[l-1][rk1[i]-1]:0);
				if(lb[bl][x-1]&&x<=a[i]&&a[i]<=y)ans-=cnt0[i][lb[bl][x-1]-1]-((l%A&&lb[bl][x-1])?cnt0[l-1][lb[bl][x-1]-1]:0);
				if(x<=a[i]&&a[i]<=y)ans+=cnt1[br-1][y]-cnt1[bl][y]-cnt1[br-1][a[i]]+cnt1[bl][a[i]];
			}
			for(int i=br*A;i<=r;++i){
				if(x<=a[i]&&a[i]<=y&&rk1[i])ans+=cnt0[i][rk1[i]-1];
				if(lb[br][x-1]&&x<=a[i]&&a[i]<=y)ans-=cnt0[i][lb[br][x-1]-1];
				if(x<=a[i]&&a[i]<=y)ans+=cnt1[br-1][a[i]]-cnt1[bl][a[i]]-cnt1[br-1][x-1]+cnt1[bl][x-1];
			}
			int lt=0,rt=0;
			for(int i=0;i<A;++i){
				if(rk0[bl][i]>=l&&x<=a[rk0[bl][i]]&&a[rk0[bl][i]]<=y)L[++lt]=rk0[bl][i];
				if(rk0[br][i]<=r&&x<=a[rk0[br][i]]&&a[rk0[br][i]]<=y)R[++rt]=rk0[br][i];
			}
			for(int i=1,t=1;i<=rt;++i){
				while(t<=lt&&a[L[t]]<a[R[i]])++t;
				ans+=t-1;
			}
			for(int i=bl+1;i<br;++i)if(lb[i][y])ans+=cp1[i][lb[i][x-1]][lb[i][y]-1];
			for(int i=bl+2;i<br;++i)
				ans+=cp0[i][i-1][lb[i][y]]-cp0[i][bl][lb[i][y]]-cp0[i][i-1][lb[i][x-1]]+cp0[i][bl][lb[i][x-1]],
				ans-=ll(cnt1[i][y]-cnt1[i-1][y]-cnt1[i][x-1]+cnt1[i-1][x-1])*(cnt1[i-1][x-1]-cnt1[bl][x-1]);
			printf("%lld\n",ans);
		}
	}
	return 0;
}