[IOI2014]holiday 假期 题解
Problem
给定一个序列,序列上每一个点有点权,给定一个出发点和一个花费,每次可以用1的花费在相邻两点间移动,也可以用1的花费获取当前点的点权的价值,每个点只可以被获取一次,求最大价值。
Solution
很容易发现,最优方法一定是先一直朝一个方向走,之后再看是否回头朝另一个方向走。
那么我们可以把这个问题分成四个小问题,我们设\(f_{1,i},f_{2.i},f_{3,i},f_{4,i}\)分别表示花费为\(i\),向左/向右/向左后回到起点/向右后回到起点的最大价值,由它们组合得到答案。
\(O(n^2logn)\)的DP是不难想到的,只要枚举最远到达的点,之后再用除去走完一遍路程的剩余的花费\(k\)在这段路程中选最大的\(k\)个价值即可,可以用主席树维护。
考虑如何去优化这个DP,我们可以发现,每当有更多的可支配花费,最优的最远到达点只增不减,具有决策单调性,这个性质也比较明显。
接下来,考虑如何实现,我们可以用决策单调性一个很常见的办法:分治来实现优化。具体而言,类似于整体二分一样。
这道题细节很多,我写了一天,心态数次炸裂,最终终于完成,下面说说几个坑点:
1.单调栈不好写这道题!我一开始写的是单调栈,最终感觉不好写,只有50pts,改换了分治。
2.主席树注意判一下查询区间是否为空。
3.\(m\)的范围不是1e5......
4.最恶心的一点,注意起点的权值不要算到两次。
Code
#include<bits/stdc++.h>
#define LL long long
using namespace std;
int n,m,k;
int d[100005],t[100005],pos[300005],root[100005];
LL Ans,f1[300005],f2[300005],f3[300005],f4[300005];//L,R,L->R,R->L
struct Segment_Tree{
int tot;
LL sum[2000005];
int seg[2000005],son[2000005][2];
void insert(int &k,int p,int l,int r,int pos){
k=++tot;
seg[k]=seg[p]+1;
son[k][0]=son[p][0];
son[k][1]=son[p][1];
sum[k]=sum[p]+t[pos];
if(l==r)
return;
int mid=l+r>>1;
if(pos<=mid)
insert(son[k][0],son[p][0],l,mid+0,pos);
else
insert(son[k][1],son[p][1],mid+1,r,pos);
return;
}
LL query(int k,int p,int l,int r,int kth){
if(l==r)
return 1LL*kth*t[l];
LL res=0;
int mid=l+r>>1;
if(seg[son[k][1]]-seg[son[p][1]]>=kth)
res+=query(son[k][1],son[p][1],mid+1,r,kth);
else{
res+=sum[son[k][1]]-sum[son[p][1]];
res+=query(son[k][0],son[p][0],l,mid+0,kth-(seg[son[k][1]]-seg[son[p][1]]));
}
return res;
}
LL Query(int l,int r,int k){
return l>r?0:query(root[r],root[l-1],1,n,min(k,r-l+1));
}
}T;
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
void solve1(int l,int r,int L,int R){
if(l>r)
return;
int mid=l+r>>1;
for(register int i=L;i<=R;++i){
if(mid-(k-i<<0)<0)
continue;
LL res=T.Query(i,k,mid-(k-i<<0));
if(!pos[mid]||res>f1[mid]){
pos[mid]=i;
f1[mid]=res;
}
}
solve1(l,mid-1,pos[mid],R);
solve1(mid+1,r,L,pos[mid]);
return;
}
void solve2(int l,int r,int L,int R){
if(l>r)
return;
int mid=l+r>>1;
for(register int i=L;i<=R;++i){
if(mid-(i-k<<0)<0)
continue;
LL res=T.Query(k,i,mid-(i-k<<0));
if(!pos[mid]||res>f2[mid]){
pos[mid]=i;
f2[mid]=res;
}
}
solve2(l,mid-1,L,pos[mid]);
solve2(mid+1,r,pos[mid],R);
return;
}
void solve3(int l,int r,int L,int R){
if(l>r)
return;
int mid=l+r>>1;
for(register int i=L;i<=R;++i){
if(mid-(k-i<<1)<0)
continue;
LL res=T.Query(i,k-1,mid-(k-i<<1));
if(!pos[mid]||res>f3[mid]){
pos[mid]=i;
f3[mid]=res;
}
}
solve3(l,mid-1,pos[mid],R);
solve3(mid+1,r,L,pos[mid]);
return;
}
void solve4(int l,int r,int L,int R){
if(l>r)
return;
int mid=l+r>>1;
for(register int i=L;i<=R;++i){
if(mid-(i-k<<1)<0)
continue;
LL res=T.Query(k+1,i,mid-(i-k<<1));
if(!pos[mid]||res>f4[mid]){
pos[mid]=i;
f4[mid]=res;
}
}
solve4(l,mid-1,L,pos[mid]);
solve4(mid+1,r,pos[mid],R);
return;
}
int main(){
n=read();k=read();m=read();++k;
for(register int i=1;i<=n;++i)
d[i]=t[i]=read();
sort(t+1,t+n+1);
for(register int i=1;i<=n;++i){
d[i]=lower_bound(t+1,t+n+1,d[i])-t;
T.insert(root[i],root[i-1],1,n,d[i]);
}
memset(pos,0,sizeof pos);solve1(1,m,max(1,k-(m>>0)),k);
memset(pos,0,sizeof pos);solve2(1,m,k,min(n,k+(m>>0)));
memset(pos,0,sizeof pos);solve3(1,m,max(1,k-(m>>1)),k);
memset(pos,0,sizeof pos);solve4(1,m,k,min(n,k+(m>>1)));
for(register int i=0;i<=m;++i)
Ans=max(Ans,max(f1[i]+f4[m-i],f2[i]+f3[m-i]));
printf("%lld\n",Ans);
return 0;
}