洛谷P8539 「Wdoi-2」来自地上的支援 题解

ST 表做法

思路

  • 当进行第 \(x\) 次操作时,若 \(b_1\)\(b_{x-1}\) 中有比 \(b_x\) 大的数,那这次操作轮不到 \(b_x\),并且前面的本来就比它大的数只会越来越大,所以以后的操作也永远轮不到 \(b_x\)

    所以,\(b_x\) 一定要不小于 \(\underset{1\le i<x}{\max}\{b_i\}\)

    问题来了:可以等于吗?

    不妨设前面最大的数为 \(b_j\),并且 \(b_x=b_j\)

    显然:\(b_j\)被操作过,也就是说,\(b_j>a_j\)。又因为\(b_x=a_x\),所以:\(a_x>a_j\)

    所以,根据规则 \(2\),当 \(b_x=b_j\ (1\le j<x)\) 时,会选择 \(b_x\) 来处理,可以等于

    这一部分的处理非常简单,不做赘述。

  • 接着考虑后面的数:若至少需操作 \(k\) 次,那么显然一定要满足 \(b_x>b_i\ (x<i<x+k)\),也就是 \(a_x+v\ (i-x)>a_i\)(这里不能等于,想法和上面类似,此处省略说明过程)。

    将这个不等式移项,得 \(a_x>a_i-v\ (i-x)\)

    因此,我们只需找出 \(\underset{x<i<x+k}{\max}\{a_i-v\ (i-x)\}\) 即可。

    我们设 \(x<i<j<x+k\),通过减法来比较他们的大小:

    \[(\ a_i-v\ (i-x)\ )-(\ a_j-v\ (j-x)\ ) =a_i-a_j-v\ (j-i) \]

    会发现,\(x\) 被消掉了,也就是说,当两数未被操作时,它们加权后的大小比较与前面的数无关

    那么,我们可以很自然地想到将 \(x_i\) 排序,使得我们不需考虑对于 \(b_{x+1}\)\(b_n\) 的修改,然后用 ST 表 \(O\ (1)\) 地求出 \(\underset{x<i<x+k}{\max}\{a_i-v\ (i-x)\}\) 对应值的下标即可(ST 表存的是下标)。

    注意:在预处理 ST 表时,若 \(a_i=a_j\ (1\le i<j\le n)\),则根据规则 \(3\) ,选择下标小的 \(i\)

最终,答案为 \(\max\ \{\ \underset{1\le i<x}{\max}\{b_i\},\underset{x<i<x+k}{\max}\{a_i-v\ (i-x)\}+1\ \}\)

ST 表预处理的时间复杂度为 \(O(n \log n)\),查询的时间复杂度为 \(O(m)\) ,总时间复杂度为 \(O(n \log n)\) ,空间复杂度为 \(O(n \log n)\)

代码

#include<bits/stdc++.h>
#define LL long long
using namespace std;
int read() {
	int s=0,f=1; char ch=getchar();
	while (ch<48 || ch>57) {if (ch=='-') f=-1; ch=getchar();}
	while (ch>=48 && ch<=57) {s=(s<<3)+(s<<1)+ch-48; ch=getchar();}
	return s*f;
}
const int N=2e6+5;
pair <int,int> q[N];
int n,m,v,a[N],b[N],w=1,max_num,st[N][20];
LL ans,ans1,ans2;
void ST_prework() {
	for (int i=1;i<=n;++i)
	st[i][0]=i;
	int t=log2(n);
	for (int k=1;k<=t;++k) {
		int len=(1<<k);
		for (int i=1;i+len-1<=n;++i) {
			if (a[st[i][k-1]]+(st[i+(len>>1)][k-1]-st[i][k-1])*v>a[st[i+(len>>1)][k-1]])
			st[i][k]=st[i][k-1];
			else st[i][k]=st[i+(len>>1)][k-1];
		}
	}
}
int query(int l,int r) {
	int k=log2(r-l+1),len=(1<<k);
	if (a[st[l][k]]+(st[r-len+1][k]-st[l][k])*v>a[st[r-len+1][k]])
	return st[l][k];
	return st[r-len+1][k];
}
int main() {
	n=read(),m=read(),v=read();
	for (int i=1;i<=n;++i)
	a[i]=read(),b[i]=a[i];
	for (int i=1;i<=m;++i)
	q[i].first=read(),q[i].second=read();
	sort(q+1,q+m+1);
	ST_prework();
	b[0]=1;
	for (int i=1;i<=n;++i) {
		while (q[w].first==i) {
			if (i+q[w].second-1>n) ans=0;
			else if (q[w].second==1) ans=b[max_num];
			else {
				int x=query(i+1,i+q[w].second-1);
				ans=max(b[max_num],a[x]-(x-i)*v+1);
			}
			ans1^=ans,ans2+=ans;
			++w;
		}
		if (b[i]>b[max_num] || b[i]==b[max_num] && a[i]>a[max_num])
		max_num=i;
		b[max_num]+=v;
	}
	printf("%lld %lld\n",ans1,ans2);
	return 0;
}

你以为这就结束了?

把这个代码交上去,你将获得 \(65\) 分,最后一个 Subtask 全部 MLE。

ST 表的空间复杂度为 \(O(n \log n)\) ,在空间只有 \(128\text{MB}\) 的情况下过不了 \(2\times10^6\),用 \(\text{sizeof}\) 算出的数组总空间为 \(183.106\text{MB}\)

屑出题人为什么只给这么点空间(恼)

空间优化

\(\text{Update}\):我写完题解才发现 \(st\) 数组的空间很多都没有利用完全,比如 \(st_{n/2,t}\)\(st_{n,t}\ (t=\log_2n)\) 是没有用的,完全可以删去。 下文的前两种优化很麻烦(虽然我感觉这种也不方便),不过第三种优化还是挺有用的。

  • 很明显,占空间最多的是 \(st\) 数组,因为该数组存的是数组的下标,不超过 \(2\times10^6\),所以可以把这个 \(\text{int}\) 数组改成一个 \(\text{unsigned char}\) 数组 \(st1\) 和一个 \(\text{unsigned short}\) 数组 \(st2\),对应的 \(st\) 值即为 \(st1\times65536+st2\)

    注:下文提到的变量类型皆为 \(\text{unsigned}\)

    数组总空间为 \(144.959\text{MB}\)

  • 还是不够,怎么办?

    \(\text{bitset}\) 或者 \(\text{bool}\)?然而,经 \(\text{sizeof}\) 的测定,用 \(\text{bitset}\) 无法优化空间;而在 \(\text{C++}\) 中,\(\text{bool}\) 的大小和 \(\text{char}\) 一样。

    因为 \(2\times10^6<2^{21}\),一个 \(\text{short}\)\(16\) 个数位,而一个 \(\text{char}\)\(8\) 个数位,\(\text{char}\) 存的数只需 \(5\) 个数位来存,所以可以把上面的方法再次优化,让 \(5\)\(\text{char}\) 中存 \(8\) 个数的信息。

    数组总空间为 \(130.654\text{MB}\)

  • ST表的空间已经被压到极限了,还是差一点,怎么办?

    \(\text{Update}\):实际上没有被压到极限。

    考虑删减其他无用数组。

    仔细思考,会发现:\(b\) 数组其实无用,可以直接在 \(a\) 数组里修改,只需开一个变量记录被修改的数的初始值即可。

    数组总空间为 \(123.024\text{MB}\)

看起来就很折磨的 AC 代码

#include<bits/stdc++.h>
#define LL long long
using namespace std;
int read() {
	int s=0,f=1; char ch=getchar();
	while (ch<48 || ch>57) {if (ch=='-') f=-1; ch=getchar();}
	while (ch>=48 && ch<=57) {s=(s<<3)+(s<<1)+(ch^48); ch=getchar();}
	return s*f;
}
const int N=2e6+5;
pair <int,int> q[N];
unsigned short st1[N][20];
unsigned char st2[1250005][20];
int n,m,v,a[N],w=1,max_num,init;
LL ans,ans1,ans2;
int st(int x,int y) {
	int res=st1[x][y];
	if (x%8==1) res+=int(st2[((x-1>>3)+1)*5-4][y]%32)*65536;
	else if (x%8==2) res+=int(st2[((x-1>>3)+1)*5-4][y]/32+(st2[((x-1>>3)+1)*5-3][y]%4)*8)*65536;
	else if (x%8==3) res+=int(st2[((x-1>>3)+1)*5-3][y]/4%32)*65536;
	else if (x%8==4) res+=int(st2[((x-1>>3)+1)*5-3][y]/128+(st2[((x-1>>3)+1)*5-2][y]%16)*2)*65536;
	else if (x%8==5) res+=int(st2[((x-1>>3)+1)*5-2][y]/16+(st2[((x-1>>3)+1)*5-1][y]%2)*16)*65536;
	else if (x%8==6) res+=int(st2[((x-1>>3)+1)*5-1][y]/2%32)*65536;
	else if (x%8==7) res+=int(st2[((x-1>>3)+1)*5-1][y]/64+(st2[((x-1>>3)+1)*5][y]%8)*4)*65536;
	else res+=int(st2[((x-1>>3)+1)*5][y]/8)*65536;
	return res;
}
void st_update(int x,int y,int z) {
	st1[x][y]=z%65536;
	if (x%8==1) st2[((x-1>>3)+1)*5-4][y]=st2[((x-1>>3)+1)*5-4][y]/32*32+z/65536;
	else if (x%8==2) {
		st2[((x-1>>3)+1)*5-4][y]=st2[((x-1>>3)+1)*5-4][y]%32+(z/65536%8)*32;
		st2[((x-1>>3)+1)*5-3][y]=st2[((x-1>>3)+1)*5-3][y]/4*4+z/65536/8;
	}
	else if (x%8==3) st2[((x-1>>3)+1)*5-3][y]=st2[((x-1>>3)+1)*5-3][y]%4+st2[((x-1>>3)+1)*5-3][y]/128*128+z/65536*4;
	else if (x%8==4) {
		st2[((x-1>>3)+1)*5-3][y]=st2[((x-1>>3)+1)*5-3][y]%128+(z/65536%2)*128;
		st2[((x-1>>3)+1)*5-2][y]=st2[((x-1>>3)+1)*5-2][y]/16*16+z/65536/2;
	}
	else if (x%8==5) {
		st2[((x-1>>3)+1)*5-2][y]=st2[((x-1>>3)+1)*5-2][y]%16+(z/65536%16)*16;
		st2[((x-1>>3)+1)*5-1][y]=st2[((x-1>>3)+1)*5-1][y]/2*2+z/65536/16;
	}
	else if (x%8==6) st2[((x-1>>3)+1)*5-1][y]=st2[((x-1>>3)+1)*5-1][y]%2+st2[((x-1>>3)+1)*5-1][y]/64*64+z/65536*2;
	else if (x%8==7) {
		st2[((x-1>>3)+1)*5-1][y]=st2[((x-1>>3)+1)*5-1][y]%64+(z/65536%4)*64;
		st2[((x-1>>3)+1)*5][y]=st2[((x-1>>3)+1)*5][y]/8*8+z/65536/4;
	}
	else st2[((x-1>>3)+1)*5][y]=st2[((x-1>>3)+1)*5][y]%8+z/65536*8;
}
void ST_prework() {
	for (int i=1;i<=n;++i)
	st_update(i,0,i);
	int t=log2(n);
	for (int k=1;k<=t;++k) {
		int len=(1<<k);
		for (int i=1;i+len-1<=n;++i) {
			if (a[st(i,k-1)]+(st(i+(len>>1),k-1)-st(i,k-1))*v>a[st(i+(len>>1),k-1)])
			st_update(i,k,st(i,k-1));
			else st_update(i,k,st(i+(len>>1),k-1));
		}
	}
}
int query(int l,int r) {
	int k=log2(r-l+1),len=(1<<k);
	if (a[st(l,k)]+(st(r-len+1,k)-st(l,k))*v>a[st(r-len+1,k)])
	return st(l,k);
	return st(r-len+1,k);
}
int main() {
	n=read(),m=read(),v=read();
	for (int i=1;i<=n;++i) a[i]=read();
	for (int i=1;i<=m;++i)
	q[i].first=read(),q[i].second=read();
	sort(q+1,q+m+1);
	ST_prework();
	a[0]=1;
	for (int i=1;i<=n;++i) {
		while (q[w].first==i) {
			if (i+q[w].second-1>n) ans=0;
			else if (q[w].second==1) ans=a[max_num];
			else {
				int x=query(i+1,i+q[w].second-1);
				ans=max(a[max_num],a[x]-(x-i)*v+1);
			}
			ans1^=ans,ans2+=ans;
			++w;
		}
		if (a[i]>a[max_num] || a[i]==a[max_num] && a[i]>init)
		max_num=i,init=a[i];
		a[max_num]+=v;
	}
	printf("%lld %lld\n",ans1,ans2);
	return 0;
}

\(1.36\text s\ /\ 123.66\text{MB}\)

写在题解后

这题我 \(20\) 分钟就写完了 \(65\) 分代码,结果调了半天才 AC。

洛谷出题人我求你别卡空间了!!!

我尝试过用 \(3\)\(\text{char}\) 数组存 \(4\) 个数的信息,再把 \(b\) 数组删掉,用 \(\text{sizeof}\) 算出的数组总空间为 \(127.793\text{MB}\),理论上能过,但是交上去还是 MLE;我的 AC 代码的数组总空间为 \(123.024\text{MB}\),但洛谷上显示最多用了 \(123.66\text{MB}\),所以不要把空间卡得太极限,实战中很容易 FST。

不过 NOIP 系列的题大多都是直接给 \(512\text{MB}\) ,很少会卡空间。

如果我老老实实用线段树写可能还不用花这么久

posted @ 2022-10-29 22:49  有素质的2B铅笔  阅读(43)  评论(0)    收藏  举报