source: BZOJ4367 IOI2014
题目描述
健佳正在制定下个假期去台湾的游玩计划。在这个假期,健佳将会在城市之间奔波,并且参观这些城市的景点。在台湾共有n个城市,它们全部位于一条高速公路上。这些城市连续地编号为0到n-1。对于城市i(0<i<n-1)而言,与其相邻的城市是i-1和i+1。但是对于城市 0,唯一与其相邻的是 城市 1。而对于城市n-1,唯一与其相邻的是城市n-2。每个城市都有若干景点。健佳有d天假期并且打算要参观尽量多的景点。健佳已经选择了假期开始要到访的第一个城市。在假期的每一天,健佳可以选择去一个相邻的城市,或者 参观所在城市的所有景点,但是不能同时进行。即使健佳在同一个城市停留多次,他也不会去重复参观该城市的景点。请帮助健佳策划这个假期,以便能让他参观尽可能多的景点。
输入格式
第1行: n, start, d.
第2行: attraction[0], …, attraction[n-1].
n: 城市数。start: 起点城市的编号。d: 假期的天数。attraction: 长度为n的数组;attraction[i] 表示城市i的景点数目,其中0≤i≤n-1。
输出格式
输出一个整数表示健佳最多可以参观的景点数。
样例输入
5 2 7
10 2 20 30 1
样例输出
60
样例说明
健佳有 7 天假期,有 5 个城市,他由城市 2 开始。在第一天,健佳参观城市2的 20 个景点。第二天,健佳由城市 2 去往城市 3。而在第三天,健佳参观城市 3 的 30 个景点。接下来的3天,健佳由城市 3 前往城市 0。而在第 7 天,健佳参观城市0的 10 个景点。这样健佳参观的景点总数是20+30+10=60,这是他由城市 2 开始、在 7 天假期内最多能参观的景点数目。
题目分析
作为一道IOI题目,它真的不难
首先会发现健佳的路线只有四种情况:一路向左,一路向右,先向左再向右,先向右再向左
否则再多一次方向变化必然会产生多余路径(这些路径上的城市都是已经被决策过是否要参观的)
于是健佳访问过的城市就形成了一个区间,我们需要做的就是找出访问这个区间内的城市有多少天是在路上的,即无法访问城市,然后在剩下的k天内访问区间内的任意城市,相当于将到达某个城市和参观景点两个连续的过程拆开来单独解决
显然我们会选出区间内前k大元素来参观,于是就可以用主席树了,当然也可以用C++的nth_element函数(这两个东西后面再填坑叭大家肯定都会哒)
可以写四个函数来处理每一种路线但没必要
考虑用start将整个区间拆成两半,于是就得到了两组左右端点,设为 l 1 l_1 l1, l 2 l_2 l2, r 1 r_1 r1, r 2 r_2 r2,那么当 l 1 = r 1 l_1=r_1 l1=r1时就是一路向右,当 l 2 = r 2 l_2=r_2 l2=r2时就是一路向左,后两种情况是根据计算k来确定的,将在代码中具体说明
都写到这里了枚举这两组端点显然不太聪明,于是考虑分治
然后就完了复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)
Code Time
#include<bits/stdc++.h>
using namespace std;
template<typename T>void read(T &s){//读入优化
s=0;
char c=' ';
int flag=1;
while(c<'0' || c>'9'){
if(c=='-')
flag=-1;
c=getchar();
}
while(c>='0' && c<='9'){
s=s*10+c-'0';
c=getchar();
}
s*=flag;
}
template<typename T>void print(T x){//输出优化虽然没有必要
if(x<0){
putchar('-');
print(-x);
}
else if(x<=9)
putchar(x+'0');
else{
print(x/10);
putchar(x%10+'0');
}
}
const int N=1e5+5;
struct segmentree{//主席树
int lc,rc,cnt;
long long sum;
}t[N*20];
int val[N],store[N],root[N],tot,n,st,d,m;
long long ans=0;
//主席树模板
void build(int l,int r,int &now){
now=++tot;
t[now].cnt=t[now].sum=0;
if(l==r) return;
int mid=(l+r)>>1;
build(l,mid,t[now].lc);
build(mid+1,r,t[now].rc);
}
void insert(int las,int &now,int l,int r,int x){
now=++tot;
t[now].lc=t[las].lc;
t[now].rc=t[las].rc;
t[now].cnt=t[las].cnt+1;
t[now].sum=t[las].sum+store[x];
if(l==r)return;
int mid=(l+r)>>1;
if(x<=mid)
insert(t[las].lc,t[now].lc,l,mid,x);
else
insert(t[las].rc,t[now].rc,mid+1,r,x);
}
long long ask(int las,int now,int l,int r,int k){
if(t[now].cnt-t[las].cnt<=k)return t[now].sum-t[las].sum;
if(l==r)return (long long)store[l]*k;
int res=t[t[now].rc].cnt-t[t[las].rc].cnt;
int mid=(l+r)>>1;
if(res>=k)return ask(t[las].rc,t[now].rc,mid+1,r,k);
else
return ask(t[las].rc,t[now].rc,mid+1,r,res)+ask(t[las].lc,t[now].lc,l,mid,k-res);
}
//分治
void solve(int ll,int lr,int rl,int rr){
if(ll>lr || rl>rr)return;//区间不存在
int mid=(ll+lr)>>1,x=rl;
long long res=0;
for(int i=rl;i<=rr;i++){//分治左端点枚举右端点
int remain=max(d-(st-mid)-(i-mid),d-(i-st)-(i-mid));//前者表示先左后右,后者表示先右后左,只用选择两者中剩余天数最多的来计算
if(remain<=0)break;
long long rs=ask(root[mid-1],root[i],0,m-1,remain);
if(rs>res)
x=i,res=rs;//保存最大值
}
if(res>ans)ans=res;//更新答案
if(ll==lr)return;//其实没什么用只是减少一层递归……
solve(ll,mid-1,rl,x);
solve(mid+1,lr,x,rr);
}
int main(){
read(n);read(st);read(d);
st++;
//主席树模板
for(int i=1;i<=n;i++){
read(val[i]);
store[i-1]=val[i];
}
sort(store,store+n);
m=unique(store,store+n)-store;
build(0,m-1,root[0]);
for(int i=1;i<=n;i++){
val[i]=lower_bound(store,store+m,val[i])-store;
insert(root[i-1],root[i],0,m-1,val[i]);
}
solve(1,st,st,n);
print(ans);
return 0;//Goodbye!
}