AT_arc217_d [ARC217D] Greedy Customer
前言
做法和官方题解不太一样。
赛时不会 C 看的 D,发现很可以增量然后复杂度均摊,然后写,死于一些 corner case,然后调了一下午,发现有个地方把 long long 赋值给了 int……
感谢 @MZMTab 同学给出的 hack。
代码在优化实现方面参考了这份记录。
解题
初步观察
题目给的求解形式看起来并不好批量计算,考虑不断将 \(k\) 加一(增量),
每次加一,最多再买(增加)一个物品,可能会放弃(删除)若干个物品,
但是删除量不会多于增加量,复杂度将是均摊正确的,
如果我们能 \(O(1)\) 求解下一次我们增加哪个物品,就可以做到 \(O(m+T(n))\),很有前途。
分析
维护一个下标单调递增的栈 \(stk\),表示当前选了哪些物品,记其大小为 \(tp\)。
考虑加入一个物品后,怎么求解下一个物品是哪一个。
物品必然来自 \([1,stk_1),(stk_1,stk_2),(stk_2,stk_3),\cdots,(stk_{tp-1},stk_{tp}),(stk_{tp},n]\),
在这些区间中选择 \(A_i\) 最小的那个作为代表,产生一个序列,(相同选择前者,ST 表解决)
但如果在这里面一个个找,复杂度无法接受,由于做法是增量,可以考虑增量时一并维护。
对代表序列中的下标为 \(i\) 的物品,计算其最早何时才能被加入,这里最早的含义是:假定下标小于 \(i\) 的都不会被加入。记作 \(B_i\)。
那么对于下标 \(i\),如果 \(B_i\) 不是前缀 \(\min\),那么最早这一条件就不成立,
而加入一个物品时,下标大于它的都会被删除(基于选择的过程可以说明),那么 \(i\) 就没有存在的必要,可以以后再考虑。
对于上述的代表序列,就可以维护一个栈 \(qp\),并要求其中 \(B_i\) 严格单调递减。
整理思路
那么,每次选择时,需要进行的维护操作是:
-
从 \(qp\) 顶弹出一个物品,即为当前选的物品,其原始下标为 \(i\)。
-
删除下标大于 \(i\) 的已选物品(对 \(stk\) 操作)。
-
设 \(i\) 前面选的一个物品为 \(p\),尝试加入 \((p,i)\) 中 \(A_i\) 最小的。(尝试意味着可能无法加入)
-
尝试加入 \((i,n]\) 中 \(A_i\) 最小的。
查询区间 \(A_i\) 最小的 \(i\),可以 ST 表维护,\(O(n\log{n})\) 预处理,\(O(1)\) 查询,可以接受。
维护当前的的花费是容易的,可以参考代码。
实现
时间复杂度 \(O(n\log{n}+m)\),空间复杂度 \(O(n\log{n})\),最慢点 935ms,可能是常数较大。
可以使用四毛子优化 ST 表,做到时间 \(O(n+m)\),空间 \(O(n)\),但可能因为常数更慢。
一些细节写在注释里了。
$\red{\text{code}}$
#include<bits/stdc++.h>
using namespace std;//判了 EOF
#define gc() (rp1==rp2&&(rp2=(rp1=buf)+fread(buf,1,IO,stdin))==rp1?EOF:*rp1++)
#define N 500005
const int IO=1<<22;
char buf[IO+1],*rp1,*rp2;
int n,m,nxt,tp=0,tq=0,st[20][N],D;
int A[N],qp[N],B[N],suf[N],stk[N];
unsigned long long ans,sum,res;
inline int read(){
int a=0,c=gc();
while(!isdigit(c)) c=gc();
while(isdigit(c)) a=10*a+c-'0',c=gc();
return a;
}
inline int mymin(int x,int y){//已保证 x<=y
return A[x]<=A[y]?x:y;
}
inline int qry(int l,int r){
if(l>r) return n+1;
int s=__lg(r-l+1);
return mymin(st[s][l],st[s][r-(1<<s)+1]);
}
void init(){
n=read(),m=read(),suf[n-1]=n,suf[n]=n+1;//suf 作用大概是常数优化,不重要
D=__lg(n),ans=0,sum=0,res=0,qp[0]=0,stk[0]=0;
for(int i=1;i<=n;i++){
st[0][i]=i,A[i]=read(),sum+=A[i];
}
for(int i=n-2;i>=1;i--) suf[i]=mymin(i+1,suf[i+1]);
for(int i=1;i<=D;i++){
for(int j=1,up=n-(1<<i)+1;j<=up;j++){
st[i][j]=mymin(st[i-1][j],st[i-1][j+(1<<i-1)]);
}
}
}
inline void ins(int x,int d){//B 代表的是最早加入时间
if(!tq||B[tq]>A[x]+d) qp[++tq]=x,B[tq]=A[x]+d;
}
void solve(){
init(),qp[tq=1]=qry(1,n),tp=0;
for(int i=A[qp[1]],up=m<sum-1?m:sum-1;i<=up;){
nxt=qp[tq--],res=i;
while(nxt<=stk[tp]) tp--;//删除 stk 中的项
if(stk[tp]+1<nxt) ins(qry(stk[tp]+1,nxt-1),i-A[nxt]);//注意在 nxt 前面,要减去 A[nxt]
if(nxt!=n) ins(suf[nxt],i);//注意要判断区间是否为空
for(stk[++tp]=nxt;i<B[tq]&&i<=up;i++) ans^=res*i;
}//为了避免一些问题,处理上界为 min(sum-1,m)
for(int i=m<sum?m+1:sum;i<=m;i++) ans^=sum*i;//Notice!不能直接把 sum 赋值给 i
printf("%llu\n",ans);
}
int main(){
for(int Tcnt=read();Tcnt;Tcnt--) solve();
return 0;
}
警示
由于实现的问题,我写下了如下代码:for(int i=sum;i<=m;i++)...;。
然而其中 sum 是 long long 类型的,直接赋值导致错误,虚空调试一下午。
引以为戒!

浙公网安备 33010602011771号