首页 > 其他分享 >holiday 假期题解(洛谷搬家)

holiday 假期题解(洛谷搬家)

时间:2023-10-07 16:47:41浏览次数:36  
标签:rt 洛谷 int 题解 tree mid ret pos holiday

P5892 holiday 假期题解

前言:

如果您想要过这一道题,需要的前置条件:

  1. 知道什么是决策单调性。
  2. 知道可持久化线段树怎么找前 $k$ 大。
  3. 有耐心看很多文字。

对于第二点,如果您不会的话,可以参考我的学习笔记(专门为过这道题做的)。

链接:https://i.cnblogs.com/posts/edit;postId=17697328

参考博客链接:https://www.luogu.com.cn/blog/hl666/solution-p5892

正解:

注释:若下面的任何一步通过看大佬的题解已经明白的,可以跳过。

  1. 四种走法:我们发现最终有可能成为答案的走法只有四种,一直向左走,一直向右走,先左走并最终走到右侧,先右走并且最终走到左侧。前两种走法我就不再解释了。对于后两种走法其中一个方面是因为你可能走到最左侧或者最右侧还有很多剩余步数可供消耗,可以用于一些折返。至于其他的走的方式,也很容易举出反例或者利用贪心证明其不是最优。

  2. 四种方程:上文可知,我们有四种走的方式,因此肯定是四种。

  3. 最初的状态的设计以及转移的思想:我们可以用 $f_i$来表示从起点走到最远的点是 $i$ 点的最佳答案。因为是最远的点,此时花费的时间最多为 $2\times dis_{start,i}$。那么我们显然可以枚举这个花费的时间,假设为 $cost$,那么我们用于参观的时间就有 $cost-dis_{start,i}$ 这么长,而这个就相当于我在区间 $[ i,start ]$ 中(假设 $i$ 小于 $start$),选择这么多个景点让我参观,贪心一点,我们肯定选前几个是吧。于是就可以用可持久化线段树来找就好。但是我们会发现时间复杂度不太对,在 $n^2\times\log n$ 这样。不仅如此,还会发现就这样设计状态的话,对于后两种很难表示(我个人认为)。

  4. 优化状态设计。换一个状态,用 $f_i$ 表示用时 $i$ 能有的最佳答案,然后再枚举最远走到的地方,而主要思想并没有变化,至于时间上的问题,可以看第五点理解。

  5. 优化:其实,仔细看看上面的第三点可以发现,我根本就没有什么转移方程,感觉像暴力一样!但实际上,我们发现,随着你分配的时间更多,我肯定是走的比上一次远更好一些。用类似整体二分的思想分治,二分时间,然后枚举决策点区间找最优,根据当前决策点依单调性保证去除没用的点就好。

  6. 详细理解优化:如果上面没看懂,可以康康这一点。让我们假设在做先向左再向右的这个情况,此时左侧折返顶点为 $X$,右侧最终到达点为 $Y$,决策单调性保证 $X$ 左侧移动时 $Y$ 也向左侧移动。要是我们已经知道左侧顶点取值范围为 $[L,R]$,我们可以分治他在 $MID$,接着用 $n$ 的时间来看 $Y$ 的位置,并在这段区间不同的前 $k$ 大中找出最有答案,然后接着二分就好啦!最后扫一遍得到答案即可。

  7. 简化代码(可跳过),发现其实正着做一遍两个转移,反着做一遍同样的两个转移是一样的涅,但是我懒得写新的主席树的函数,就不想这么做涅。

代码:

其实感觉和其他大佬的代码没啥大的区别,关键还是要靠理解啊!

#include<bits/stdc++.h>
using namespace std;
int num[250005];
int lsh[250005];
int buc[250005];
int cnt=0;
vector<int>root;
struct node{
	int rs;
	int ls;
	long long sum;
	int val;
}tree[2000005];
inline int addnode(int x){
	int ret=++cnt;
	tree[ret]=tree[x];
	return ret;
}
inline void pushup(int rt){
	tree[rt].val=tree[tree[rt].ls].val+tree[tree[rt].rs].val;
	tree[rt].sum=tree[tree[rt].ls].sum+tree[tree[rt].rs].sum;
}
inline void build(int rt,int l,int r){
	if(l==r){
		tree[rt].val=0;
		return;
	}
	tree[rt].ls=++cnt;
	tree[rt].rs=++cnt;
	int mid=(l+r)>>1;
	build(tree[rt].ls,l,mid);
	build(tree[rt].rs,mid+1,r);
	
}
inline int change(int rt,int x,int val,int L,int R){
	int now=addnode(rt);
	int le=L;
	int ri=R;
	if(le==ri){
		tree[now].val=val;
		tree[now].sum=1ll*val*lsh[x];
		return now;
	}
	int mid=(le+ri)>>1;
	if(x<=mid){
		tree[now].ls=change(tree[now].ls,x,val,L,mid);
	}
	else{
		tree[now].rs=change(tree[now].rs,x,val,mid+1,R);
	}
	pushup(now);
	return now;
}
inline long long get(int Lrt,int Rrt,int k,int L,int R){//求前k大的和 
	int le=L;
	int ri=R;
	if(le==ri){
		return 1ll*lsh[le]*k;
	}
	int Lls=tree[Lrt].ls;
	int Lrs=tree[Lrt].rs;
	int Rls=tree[Rrt].ls;
	int Rrs=tree[Rrt].rs;
	int rsum=tree[Rrs].val-tree[Lrs].val;
	int mid=(L+R)>>1;
	if(rsum<k){
		return 1ll*(tree[Rrs].sum-tree[Lrs].sum+get(Lls,Rls,k-rsum,L,mid));
	}
	else{
		return 1ll*get(Lrs,Rrs,k,mid+1,R);
	}
}
int n,s,d;
inline long long findkth(int le,int ri,int k){
	if(k==0||le>ri){
		return 0;
	}
	k=min(k,ri-le+1);
	return 1ll*get(root[le-1],root[ri],k,1,n);
}
//----------------------------------------以上是打的可持久化线段树 
int pos[250005];
long long f1[250005],f2[250005],f3[250005],f4[250005];

inline void clear(){
	memset(pos,0,sizeof(pos));
}
inline void solveleft(int begin,int ed,int l,int r){//看英文名应该懂我在干啥吧 
	if (l>r){
		return; 
	}
	int mid=(l+r)>>1;
	for(int i=begin;i<=ed;i++){
	 	if (mid-(i-s)>=0){
			long long ret=findkth(s,i,mid-(i-s));
			
			if (!pos[mid]||ret>f1[mid]){
				f1[mid]=ret;
				pos[mid]=i;
			} 
		}
	}

	solveleft(begin,pos[mid],l,mid-1);
	solveleft(pos[mid],ed,mid+1,r);
}
inline void solverl(int begin,int ed,int l,int r){
	if (l>r){
		return; 
	}
	int mid=(l+r)>>1;
	for(int i=begin;i<=ed;i++){
	 	if (mid-((i-s)*2)>=0){
			long long ret=findkth(s,i,mid-(i-s)*2);
			if (!pos[mid]||ret>f2[mid]){
				f2[mid]=ret;
				pos[mid]=i;
			} 
		}
	}

	solverl(begin,pos[mid],l,mid-1);
	solverl(pos[mid],ed,mid+1,r);
}
inline void solveright(int begin,int ed,int l,int r){

	if (l>r){
		return; 
	}
	int mid=(l+r)>>1;
	for(int i=begin;i<=ed;i++){
		
	 	if (mid-((s-i))>=0){
			long long ret=findkth(i,s-1,mid-(s-i));
//			cout<<"TEST"<<i<<" "<<s-1<<" "<<mid-(s-i)<<" "<<ret<<endl;
			if (!pos[mid]||ret>f3[mid]){
				f3[mid]=ret;
				pos[mid]=i;
			} 
		}
	}
	solveright(pos[mid],ed,l,mid-1);
	solveright(begin,pos[mid],mid+1,r);
	
} 
inline void solvelr(int begin,int ed,int l,int r){
	if (l>r){
		return; 
	}
	int mid=(l+r)>>1;
	for(int i=begin;i<=ed;i++){
	 	if (mid-((s-i)*2)>=0){
			long long ret=findkth(i,s-1,mid-(s-i)*2);
			if (!pos[mid]||ret>f4[mid]){
				f4[mid]=ret;
				pos[mid]=i;
			} 
		}
	}
	solvelr(pos[mid],ed,l,mid-1);
	solvelr(begin,pos[mid],mid+1,r);
}
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*10+ch-'0',ch=getchar();
    return x*f;
}
void write(long long x)
{
    if(x<0)
        putchar('-'),x=-x;
    if(x>9)
        write(x/10);
    putchar(x%10+'0');
    return;
}


int main(){
//	ios::sync_with_stdio(false);
	
//	cin >> n >> s >> d;
	n=read();
	s=read();
	d=read();
	s++;
	for(int i=1;i<=n;i++){
		num[i]=read();
		lsh[i]=num[i];
	}
	root.push_back(0);
	build(0,1,n);
	sort(lsh+1,lsh+1+n);
	int cntt=unique(lsh+1,lsh+1+n)-lsh-1;
	for(int i=1;i<=n;i++){
		num[i]=lower_bound(lsh+1,lsh+1+cntt,num[i])-lsh;
		root.push_back(change(root[i-1],num[i],++buc[num[i]],1,n));
	}
	//可持久化线段树模板
	findkth(3,5,4);
	clear();
	solveleft(s,min(s+d,n),1,d);
	clear();
	solverl(s,min(s+(d*2),n),1,d);
	clear();
	solveright(max(1,s-d),s,1,d);
	clear();
	solvelr(max(1,s-d*2),s,1,d);
	long long ans=0;
//	cout<<cnttt<<endl;
//	for(int i=0;i<=d;i++){
//		cout<<f1[i]<<" "<<f2[i]<<" "<<f3[i]<<" "<<f4[i]<<endl;
//	}
	for(int i=0;i<=d;++i){
		ans=max(ans,max(f1[i]+f4[d-i],f2[i]+f3[d-i]));
	}
	cout<<ans;
	
}

后记:

这道题要算好主席树的大致空间限制,以及要记得由于我们状态和时间有关,应该开到 $n$ 的 $3$ 倍左右比较保险。

标签:rt,洛谷,int,题解,tree,mid,ret,pos,holiday
From: https://www.cnblogs.com/linghusama/p/17746636.html

相关文章

  • [TJOI2018] 游园会题解
    [TJOI2018]游园会(dp套dp)目录[TJOI2018]游园会(dp套dp)前言:题目简化:解题思路:较为简单的一步:较为困难的步骤思路总结代码呈现:注释/后记:前言:这是和dp套dp的初遇,这不得好好了解一下。题目简化:先把题目进行简化,就是要构造字符串,对于$len\in[0,k]$满足以下条件:只包含......
  • 【洛谷 P1739】表达式括号匹配 题解(栈)
    表达式括号匹配题目描述假设一个表达式有英文字母(小写)、运算符(+、-、*、/)和左右小(圆)括号构成,以@作为表达式的结束符。请编写一个程序检查表达式中的左右圆括号是否匹配,若匹配,则输出YES;否则输出NO。表达式长度小于,左圆括号少于个。输入格式一行:表达式。输出格式一行:YES或NO......
  • ip实验:ospf和isis共存下的问题解决
    一,实验目的:内网正常访问ar4的两个外部静态路由地址二,实验配置思路:引入外部静态后,在ar2上引入带isis里面,会发现ar1是个故障节点(只要是访问外部路由经过该节点时就会发生环路),在ar1上拒绝对应的isis路由的加表(不是双点双向啊,因为ar1上有isis进程,isis的路由表会和ospf路由表抢着加入......
  • 【倍增】ABC212F Greedy Takahashi 题解
    ABC212F暴力就是直接跳,显然不可过。考虑对暴力进行优化,发现整个图是不会改变的,容易想到使用倍增。显然是对边进行倍增的,令\(f_{i,j}\)表示从第\(i\)条边开始,跳了\(2^j\)条边后,到的是哪一条边,如果不存在,则设为\(-1\)。然后就是很显然的倍增了,最后讨论一下即可。时间复......
  • 【主席树】P8201 [传智杯 #4 决赛] [yLOI2021] 生活在树上(hard version)题解
    P8201简单题。题中求的是\(dis_{a,t}\oplusdis_{t,b}=k\)是否存在,显然不好直接维护,考虑转化。令\(dist=dis_{a,t}\oplusdis_{t,b}\),\(val=\bigoplus\limits_{x\in\text{path}(a,b)}w_x\)。如果\(t\)在\(\text{path}(a,b)\)上,则\(dist=val\oplus......
  • 【二分图】CF1139E Maximize Mex 题解
    CF1139E翻译中有一句话:校长将会从每个社团中各选出一个人。就是一些人被分为一组,从每组中选一些人出来。这就很容易想到通过二分图的匹配。\(\text{mex}\)运算有一个显而易见的贪心:枚举每个值能否被匹配,第一个找不到的值就是答案。由于\(\text{mex}\)运算的值域与\(n\)......
  • 网络规划设计师真题解析--TCP慢启动拥塞避免机制
    TCP使用慢启动拥塞避免机制进行拥塞控制。当拥塞窗口大小为16时,发送节点出现超时未收到确认现象时,将采取的措施是(26)。再经过5轮后的拥塞窗口大小为(27)。26、A.将慢启动阈值设为16,将拥塞窗口设为8,并进入拥塞避免阶段B.将慢启动阈值设为16,将拥塞窗口设为1,并进入慢开始阶段C.将慢启动......
  • 网络规划设计师真题解析--TCP慢启动拥塞避免机制
    TCP使用慢启动拥塞避免机制进行拥塞控制。当拥塞窗口大小为16时,发送节点出现超时未收到确认现象时,将采取的措施是(26)。再经过5轮后的拥塞窗口大小为(27)。26、A.将慢启动阈值设为16,将拥塞窗口设为8,并进入拥塞避免阶段B.将慢启动阈值设为16,将拥塞窗口设为1,并进入慢开始阶段C.将慢启动阈......
  • [题解] CF1245D - Shichikuji and Power Grid
    CF1245D-ShichikujiandPowerGrid题目传送门题意在一个网格图中,有\(n\)个城市。目标是使得\(n\)个城市都通电。对于一个城市有电,要么选择在其位置建立发电站,要么和另一个有电的城市连线。对于城市\(i\),在其位置建立发电站的费用为\(c_i\),和另一个城市\(j\)连电......
  • 【题解】洛谷#P7073 [CSP-J2020] 表达式
    【题解】洛谷#P7073[CSP-J2020]表达式Description给定一个逻辑表达式和其中每一个操作数的初始取值后,再取反某一个操作数的值时,求出原表达式的值。表达式将采用后缀表达式的方式输入。Solution根据题目可得,当取反一个操作数的值时,整个表达式大体只有变与不变两种情况,而往下......