洛谷 P5391

洛谷题面传送门

介绍一种假做法,期望复杂度应该比较优秀,但可以卡掉(

首先这个问题显然严格强于只有添加元素的情况对吧,而只有添加元素的情况就是一个普通的背包,而只有插入操作的版本复杂度就已经达到了 (mathcal O(qV)),因此此题 std 的复杂度肯定不低于 (mathcal O(qV)),而此题数据范围和实现刚好够 (mathcal O(qV)) 的做法通过,因此我们考虑往这个方向思考。我们可以想出一车奇奇怪怪地做法,但都过不去此题的限制,譬如:

  • 奇奇怪怪的做法 (1):显然此题后进先出的结构组成了一个栈对吧,因此我们考虑对于每个物品,记录栈底到该物品的背包情况,这样删除操作就移动个指针即可,时空复杂度均为 (mathcal O(qV)),但由于此题空间限制很紧,该做法无法通过。
  • 奇奇怪怪的做法 (2):注意到每个物品存活的时间肯定是一个区间,因此考虑以时间轴为下标建立一棵线段树进行线段树分治,线段树向下递归时就将对应节点上的物品加入背包,回溯时直接记录个临时数组并撤回即可,时间复杂度 (qVlog q),空间复杂度 (Vlog q),时间复杂度又无法卡进实现。

考虑将两个暴力结合一下。常用的结合暴力的做法无非就是利用根号的良好性质,因此我们考虑分块,我们设一个阈值 (B),然后将已经插入的元素分为两部分:整块和零散部分,具体来说假设插入的元素个数为 (x),那么我们将前 (lfloordfrac{x}{B} floor·B) 个元素分为 (lfloordfrac{x}{B} floor) 个整块,外加 (xmod B) 个散块。对于每个整块我们维护一下栈底到这个整块的右端点构成的背包,对于散块我们维护一下散块中前 (i) 个元素((ile xmod B))组成的背包,每加入一个元素,如果散块大小达到了 (B) 那就暴力将散块打包为一个整块,否则直接在散块最后一个元素末尾组成的背包中加入一个元素塞到背包序列的末尾。每次弹出栈顶元素时,如果当前散块被弹空了则暴力重构最后一个整块,否则直接令散块大小 (-1),这样一来,如果 (B) 固定那么显然是可以卡掉的,每次先插入 (B) 个元素然后不断删除,再插入,再删除,再插入,以此类推即可将复杂度卡到 (qVB),但是如果我们考虑玄学一波,(B)([101,200]) 中随机,这样复杂度期望就是 (qV) 了(

极端数据大概是先插入 (100) 个元素,然后插入、删除,重复 (100) 次,再插入一个元素,插入、删除,重复 (100) 次,以此类推,这样不论 (B) 是啥都要重构同一块重构 (100) 次(

貌似题解区里也有神仙的做法复杂度与根号相关?并且还有严格的复杂度证明?orzorz/bx

const int MAXN=2e4;
int qu,V,blk_sz,cur=0,rst=0;
pii a[MAXN+5];
struct knap{
	int w[MAXN+5];
	void clear(){memset(w,0,sizeof(w));}
	void ins(int x,int y){for(int i=x;i<=V;i++) chkmax(w[i],w[i-x]+y);}
} b[205],ed[205];
int calc(){
	int res=0;
	for(int i=0;i<=V;i++) chkmax(res,b[cur/blk_sz].w[i]+ed[rst].w[V-i]);
	return res;
}
int main(){
	scanf("%d%d",&qu,&V);srand(time(0));
	blk_sz=rand()%100+101;
	while(qu--){
		static char opt[11];scanf("%s",opt+1);
		if(opt[1]=='a'){
			int x,y;scanf("%d%d",&x,&y);
			a[++cur]=mp(x,y);rst++;
			ed[rst]=ed[rst-1];ed[rst].ins(x,y);
			if(rst==blk_sz){
				b[cur/blk_sz]=b[cur/blk_sz-1];
				for(int i=cur-blk_sz+1;i<=cur;i++) b[cur/blk_sz].ins(a[i].fi,a[i].se);
				rst=0;
			}
		} else {
			cur--;rst--;
			if(rst<0){
				for(int i=1;i<=blk_sz;i++) ed[i].clear();
				rst=blk_sz-1;
				for(int i=1;i<blk_sz;i++) ed[i]=ed[i-1],ed[i].ins(a[cur-rst+i].fi,a[cur-rst+i].se);
			}
		} printf("%d
",calc());
	}
	return 0;
}
/*
4 8
add 2 1
add 3 2
add 4 3
add 5 4
*/

最后稍微提一下正解,大概就你可以把一个入栈弹栈序列与一棵树对应,这样建出树出来,等价于有一棵节点数为 (mathcal O(q)) 的树,树上每个点有一个物品,要求每个点到根路径上的物品组成的背包。我们对树进行树链剖分,然后对于每个点我们记录下它到根节点的每个重链的前缀组成的背包,那么对于每个点我们先遍历一遍它的轻儿子,这样这个链的前缀背包就是这个点的答案,然后再遍历它的重儿子将它重儿子对应的物品加入这个重链的背包,不难发现由于每个点到根节点重链个数是 (log q) 级别的,因此空间复杂度是 (Vlog q) 的,而由于每个点的贡献最多被加入一次,因此加入的复杂度为 (mathcal O(qV)),刚好符合此题的限制。

代码?咕了~~

原文地址:https://www.cnblogs.com/ET2006/p/luogu-P5391.html