【题解】holiday (分治+主席树)

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!
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值