[LOJ6515]贪玩蓝月

题目

传送门 to LOJ

思路

我吐了……我直接分块然后复杂度算错了…… 😢

而且 线段树分治 的题很久没做过了。操蛋极了。我只觉得身心俱疲,如同在水里泡了三天一般绵软。

抛开上面的离线做法,我们有一个在线的做法。线段树分治的本质也是 背包只有加入与撤销。而对于某一端而言,着实如此。可不可以用 两个栈 来分开维护两端呢?

问题就在于某个栈删除干净的情况。这个情况直接 瞎几把乱搞!将另一个栈中的元素 平均拆成两半,一边塞一半,重新计算。复杂度会不会很烂?答案是不会。如果你用 O ( x p ) \mathcal O(xp) O(xp) 的时间将 x x x 个元素重新塞到了两边,那么,你重新激发 “拆半” 就需要花费 x 2 \frac{x}{2} 2x 个步骤将某一边删除干净。明显是个二倍关系。

总的步骤数量是 m m m 的,所以除了最后一次 “拆半”,总时间开销为 2 m p = O ( m p ) 2mp=\mathcal O(mp) 2mp=O(mp) 。最后一次的时间开销也不会超过这玩意儿。

如何统计答案?无非就是两个背包拼起来, i + j ∈ [ l , r ] i+j\in[l,r] i+j[l,r] 。枚举 i i i 之后 j j j 的可行取值就是滑动窗口。所以可以 O ( p ) \mathcal O(p) O(p) 的得到答案。于是复杂度还是 O ( m p ) \mathcal O(mp) O(mp) 的。

代码

最慢的跑了 0.543 s 0.543\text{s} 0.543s 😉

#include <cstdio>
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
typedef long long int_;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
template < typename T >
void getMax(T&a,const T&b){
	(a < b) ? (a = b) : 0;
}

const int MaxN = 50005;
const int MaxP = 500;
const int_ infty = (1ll<<60)-1;
int a[MaxN<<1], b[MaxN<<1];

int_ tmp[MaxP]; int n, p;
struct Package{
	int_ dp[MaxP];
	void clear(){
		for(int i=1; i<p; ++i)
			dp[i] = -infty;
		dp[0] = 0;
	}
	void add(int i){
		for(int j=0; j<p; ++j)
			tmp[(j+a[i])%p] = dp[j]+b[i];
		for(int j=0; j<p; ++j)
			getMax(dp[j],tmp[j]);
	}
};
Package bb[MaxN<<1|1]; // 背包

const int dir[] = {-1,1};
int now[2] = {MaxN-1,MaxN+1};
void rebuild(int x){
	int len = now[1]-now[0]-1, cur;
	if(x == 0) cur = MaxN-len/2;
	else cur = MaxN+(len+1)/2;
	for(int i=now[x]-dir[x];
		i!=now[x^1]; i-=dir[x]){
			a[cur] = a[i], b[cur] = b[i];
			cur -= dir[x]; // 同向移动
			if(cur == MaxN) // 跳过
				cur -= dir[x];
		}
	now[0] = MaxN-(len>>1)-1;
	now[1] = MaxN+(len+1)/2+1;
	for(int j=0; j<2; ++j)
	for(int i=MaxN+dir[j]; i!=now[j]; i+=dir[j])
		bb[i] = bb[i-dir[j]], bb[i].add(i);
}

int dl[MaxP<<1], head, tail; // 队列
void insert(int_ zxy[],int i){
	while(head <= tail &&
		zxy[dl[tail]] <= zxy[i])
			-- tail; // pop_back()
	dl[++ tail] = i;
}
void query(int l,int r){
	int_ *sxy = bb[now[0]+1].dp;
	int_ *zxy = bb[now[1]-1].dp;
	head = 0, tail = -1;
	for(int i=r; i>l; --i)
		insert(zxy,i);
	int_ ans = -1;
	for(int i=0; i<p; ++i){
		while(head <= tail && 
			dl[head] == (r+1+p-i)%p)
				++ head; // pop_F
		insert(zxy,(l+p-i)%p);
		ans = max(ans,sxy[i]+
			zxy[dl[head]]);
	}
	printf("%lld\n",ans);
}

char opt[10];
int main(){
	readint(); // get away, b**ch
	n = readint(), p = readint();
	bb[MaxN].clear(); // base
	for(int x; n; --n){
		scanf("%s",opt);
		if(opt[0] == 'I'
		or opt[0] == 'D'){
			if(opt[1] == 'F') x = 0;
			if(opt[1] == 'G') x = 1;
		}
		if(opt[0] == 'I'){
			a[now[x]] = readint()%p;
			b[now[x]] = readint();
			bb[now[x]] = bb[now[x]-dir[x]];
			bb[now[x]].add(now[x]);
			now[x] += dir[x]; // move
		}
		if(opt[0] == 'D'){
			now[x] -= dir[x];
			if(now[x] == MaxN){ // run out
				now[x] -= dir[x];
				rebuild(x); // which side
			}
		}
		if(opt[0] == 'Q'){
			x = readint();
			query(x,readint());
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值