题目
思路
我吐了……我直接分块然后复杂度算错了…… 😢
而且 线段树分治 的题很久没做过了。操蛋极了。我只觉得身心俱疲,如同在水里泡了三天一般绵软。
抛开上面的离线做法,我们有一个在线的做法。线段树分治的本质也是 背包只有加入与撤销。而对于某一端而言,着实如此。可不可以用 两个栈 来分开维护两端呢?
问题就在于某个栈删除干净的情况。这个情况直接 瞎几把乱搞!将另一个栈中的元素 平均拆成两半,一边塞一半,重新计算。复杂度会不会很烂?答案是不会。如果你用 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;
}