P1311 选择客栈

本文深入探讨了线段树和ST表两种数据结构在区间最小值查询问题上的应用。通过具体实例,详细介绍了这两种方法的实现过程和代码细节,对比了它们的时间效率,并分享了在洛谷平台上的评测结果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目
这道题目写得我真舒畅,一遍过。
由题意可以很轻松的得出这道题目是要查询区间最小值。
众所周知有两种非常普遍的方法;

ST表

线段树

这里要隆重介绍的是线段树,线段树有很良好的性质。就是完全二叉树的一些性质。
如图完全二叉树
可以观察出 左儿子的编号是父节点编号的两倍,右儿子的编号是父节点编号的两倍加一。
这也是线段树要运用的最主要的东西。
分析完该用什么算法后接着来看思路
判定一个区间的最小值是否小于等于p,若满足条件则答案加一。
接下来上代码

#include<bits/stdc++.h>
using namespace std;
const int ll=2e6+1;
int n,k,p;
int w[ll];
vector <int> e[51];//用于存储相同色调的客栈的编号
struct node{
	int l,r,data;
}t[ll<<2];//线段树4倍存储
void build(int p,int l,int r){
	t[p].l=l; t[p].r=r;
	if(l==r){
		t[p].data=w[l];
		return ;
	}
	int mid=(l+r)>>1;
	build(p<<1,l,mid);
	build((p<<1)|1,mid+1,r);
	t[p].data=min(t[p<<1].data,t[(p<<1)|1].data);
}
int ask(int p,int l,int r){
	if(l<=t[p].l&&r>=t[p].r){
		return t[p].data;
	}
	int mid=(t[p].l+t[p].r)/2,ans=1<<30;
	if(l<=mid) ans=min(ans,ask(p<<1,l,r));
	if(r>mid) ans=min(ans,ask((p<<1)|1,l,r));
	return ans;
}
int main(){
	scanf("%d%d%d",&n,&k,&p);
	for(int i=1;i<=n;i++){
		int c;
		scanf("%d%d",&c,&w[i]);
		e[c].push_back(i);
	}
	build(1,1,n);
	int res=0;
	for(int i=0;i<k;i++){
		for(int j=0;j<e[i].size();j++){
			int a=e[i][j];
			for(int x=j+1;x<e[i].size();x++){
				int b=e[i][x];
				if(ask(1,a,b)<=p){
					res+=(e[i].size()-x);
					break;//由于相同色调的客栈的编号在数组中是单调的,所以只要小范围满足,大范围一定满足。
				}				
			}
		}
	}
	cout<<res<<endl;
	return 0;
}

过了几天我决定补上ST表的做法,用洛谷评测了一下时间效率差不多
众所周知ST表可以维护静态区间最值
最大值可以,同理最小值也同样适用
算法流程
1将同样色调的客栈存入一个队列里,
2枚举同样色调客栈的组合方法,用 ST表 判定最小费用是否小于p。
这里再不冲一个小技巧
若一个小区间满足条件,那么包含它的区间也一定满足。
上代码

#include<bits/stdc++.h>
using namespace std;
const int ll=2000010;
int n,k,p,f[ll][30],a[ll];
vector<int> v[550];
int ask(int l,int r){
	int k=log(r-l+1)/log(2);
	return min(f[l][k],f[r-(1<<k)+1][k]);	
}
int main(){
	scanf("%d%d%d",&n,&k,&p);
	for(int i=1;i<=n;i++){
		int c;
		scanf("%d%d",&c,&a[i]);
		v[c].push_back(i);		
	}
	for(int i=0;i<k;i++)
		sort(v[i].begin(),v[i].end());
	int t=log(n)/log(2)+1;
	for(int i=1;i<=n;i++) f[i][0]=a[i];
	for(int i=1;i<t;i++)
		for(int j=1;j<n-(1<<i)+1;j++)
			f[j][i]=min(f[j][i-1],f[j+(1<<(i-1))][i-1]);
	int ans=0;
	for(int i=0;i<k;i++)
		for(int j=0;j<v[i].size();j++){
			int x=v[i][j];
			for(int k=j+1;k<v[i].size();k++){
				int y=v[i][k]; 
				int minn=ask(x,y);
				if(minn<=p){
					ans+=v[i].size()-k;		
					break;					
				}				
			}
		}
	printf("%d\n",ans);	
	return 0;
}
题目描述 丽江河边有 n n 家很有特色的客栈客栈按照其位置顺序从 1 1 到 n n 编号。每家客栈都按照某一种色调进行装饰(总共 k k 种,用整数 0 ∼ k − 1 0∼k−1 表示),且每家客栈都设有一家咖啡店,每家咖啡店均有各自的最低消费。 两位游客一起去丽江旅游,他们喜欢相同的色调,又想尝试两个不同的客栈,因此决定分别住在色调相同的两家客栈中。晚上,他们打算选择一家咖啡店喝咖啡,要求咖啡店位于两人住的两家客栈之间(包括他们住的客栈),且咖啡店的最低消费不超过 p p 。 他们想知道总共有多少种选择住宿的方案,保证晚上可以找到一家最低消费不超过 p p 元的咖啡店小聚。 输入格式 共 n + 1 n+1 行。 第一行三个整数 n , k , p n,k,p,每两个整数之间用一个空格隔开,分别表示客栈的个数,色调的数目和能接受的最低消费的最高值; 接下来的 n n 行,第 i + 1 i+1 行两个整数,之间用一个空格隔开,分别表示 i i 号客栈的装饰色调 a i a i ​ 和 i i 号客栈的咖啡店的最低消费 b i b i ​ 。 输出格式 一个整数,表示可选的住宿方案的总数。 输入输出样例 #1 输入 #1 5 2 3 0 5 1 3 0 2 1 4 1 5 输出 #1 3 说明/提示 样例解释 2 人要住同样色调的客栈,所有可选的住宿方案包括:住客栈①③,②④,②⑤,④⑤,但是若选择住 4 , 5 4,5号客栈的话, 4 , 5 4,5 号客栈之间的咖啡店的最低消费是 4 4 ,而两人能承受的最低消费是 3 3 元,所以不满足要求。因此只有前 3 3 种方案可选。 数据范围 对于 30 % 30% 的数据,有 n ≤ 100 n≤100 ; 对于 50 % 50% 的数据,有 n ≤ 1   000 n≤1000; 对于 100 % 100% 的数据,有 2 ≤ n ≤ 2 × 10 5 2≤n≤2×10 5 , 1 ≤ k ≤ 50 1≤k≤50, 0 ≤ p ≤ 100 0≤p≤100, 0 ≤ b i ≤ 100 0≤b i ​ ≤100。
03-31
<think>嗯,我现在得仔细看看这个问题。题目大意是说,有n家客栈,按照顺序排开,每家都有一个色调和最低消费。两位游客要住在同一色调的客栈里,而且在他们之间的客栈(包括自己住的)至少有一个咖啡店的最低消费不超过p。需要计算这样的住宿方案总数。 首先,我得理解题目中的条件。两人必须住在同一色调的客栈i和j(i<j),并且在i到j之间至少存在一个客栈的消费≤p。那我的目标就是找出所有满足这两个条件的i和j的组合。 那怎么高效计算呢?如果直接暴力枚举所有可能的i和j,然后检查中间是否有满足条件的客栈,那时间复杂度会是O(n²),对于n=2e5来说肯定不行。所以必须想更高效的方法。 可能的思路是,对于每个客栈j,找到左边最近的满足条件的客栈i,或者在i到j之间有满足条件的。或者,可以维护每个颜色的一些信息,比如记录之前出现过的该颜色客栈的位置,以及在这些位置之后是否有满足条件的消费。 例如,对于每个颜色k,维护一个列表,保存所有属于该颜色的客栈的位置。同时,记录每个位置之前或之后最近的消费≤p的位置。这样,当处理到某个客栈j时,可以快速找到在它之前同颜色的客栈i,且i到j之间有满足条件的客栈。 或者,可以分情况讨论:当某个客栈j的消费b[j]≤p时,那么对于所有同颜色的客栈i(i<j),只要i到j之间存在这个j,那么i和j的组合都满足条件。或者,即使i到j之间还有其他满足条件的点,只要存在至少一个即可。这时候可能需要统计在之前同颜色的客栈中,有多少个的位置i满足i到j之间有至少一个消费≤p的客栈。 另一种思路是,维护每个颜色的最近一次出现的位置,并且在遍历的时候记录最近的消费≤p的位置。这样,当遍历到j时,如果当前客栈的消费≤p,那么对于每个颜色,之前所有的该颜色的客栈都可以与j组成合法对,因为中间包含了j自己。如果当前客栈的消费>p,那么只能和那些在该颜色最后一次出现在某个满足条件的位置之后的客栈配对。 或者,可以这样想:对于每个客栈j,找到左边最近的满足消费≤p的位置last。然后,在颜色为a[j]的所有客栈中,统计在last的位置之前(或者之后)出现的次数。这可能涉及到维护每个颜色的出现次数,并且记录在last之前的数目。 比如,假设我们维护一个数组cnt[k]表示颜色k的客栈数量。同时,维护一个last_pos,记录最近的一个消费≤p的位置。当遍历到j时,如果当前客栈的消费≤p,那么对于颜色a[j]来说,所有之前出现的该颜色客栈都可以和j组成合法对。同时,如果当前客栈消费>p,那么只能和那些在last_pos之后出现的该颜色客栈配对,因为last_pos是最近的满足条件的点,所以j到last_pos之间的区域是否覆盖i的位置? 或者,另一种更具体的方法:维护一个数组color_counts[k],表示颜色k的客栈数量。另外,维护一个数组last_valid[k],记录颜色k在最近一个消费≤p的位置之后出现的次数。或者,每次遇到一个消费≤p的客栈时,记录当前各颜色的数量,这样当之后遇到同颜色客栈时,可以知道有多少个在最近的有效点之后的同颜色客栈。 可能更具体的方法是: 遍历客栈时,维护一个数组last[k],记录颜色k最近一次出现的位置,并且维护一个变量prev_valid,记录最近的一个消费≤p的位置。 当处理到当前客栈j时,如果当前的b[j]≤p,那么对于颜色a[j],所有之前出现过的同颜色客栈都可以与j配对,因为中间的j自己满足条件。这时候,颜色a[j]的数目是之前记录的该颜色的数目,加上可能其他情况? 或者,假设我们维护一个数组color_count[k]表示在当前有效区间内颜色k的数目。例如,每当遇到一个消费≤p的客栈时,这个客栈可以作为分界点,将之前的颜色计数累加到对应的color_count中,或者重置? 这可能比较复杂。或者,可以这样做: 当遍历每个客栈时,维护一个数组cnt[k],表示在当前有效点之后,颜色k的客栈数目。这里的有效点指的是最近的消费≤p的位置。 当遇到一个客栈j,如果其b[j]<=p,那么对于后面的客栈来说,只要颜色相同,并且位于j之后,那么他们之间的有效点就是j或者更后面的有效点。因此,此时可以将cnt数组重置为各个颜色到目前为止的总数吗?或者需要记录每个颜色在之前各个有效点的数目? 比如,假设我们有一个变量latest_valid,表示最近的满足b<=p的位置。当处理客栈j时,如果j的b<=p,那么所有颜色为a[j]的客栈在latest_valid之后到j的位置之间的,都可以和j之后的同色客栈配对? 或者,我们可以维护一个数组last[k],其中last[k]保存颜色k在最近的有效区间内的出现次数。具体来说,当遇到一个有效点(b<=p)时,更新last数组为当前各个颜色的累计数目。然后,当处理后续的客栈j时,如果j的颜色是c,那么当前客栈可以与last[c]中的数目组成有效对,因为last[c]中的数目是在最近的有效点之前出现的该颜色的数目,因此j和这些数目中的每一个都满足中间有有效点。 举个例子,比如,遍历到j时,如果j的b<=p,那么此时,所有之前同颜色c的客栈,只要是在上一次有效点之后出现的,都可以和j组成有效对。或者说,当遇到一个有效点j时,可以记录此时各个颜色出现的总次数到另一个数组,比如sum[k]。然后,对于后面的客栈i,如果i的颜色是c,那么sum[c]就是所有在有效点j之前出现的颜色c的次数。因此,当i的客栈出现在j之后,并且颜色c,那么这些i可以与sum[c]中的数目相乘,得到可能的对数? 或者,每次遇到有效点j时,记录当前各个颜色的累计数目到数组valid_counts中。之后,每当遇到颜色c的客栈i时,可以将valid_counts[c]加到总结果中。这样,因为i和之前所有在有效点j之前的颜色c的客栈之间,存在j作为有效点,所以满足条件。 这种方法的时间复杂度是O(nk),但k是50,所以对于n=2e5来说是可行的。例如: 初始化一个数组sum_color,记录各个颜色的总数。 初始化一个数组valid_counts,记录在最近的有效点之前的各颜色数目。 当遍历到客栈j时: 如果b[j] <= p: 那么,对于当前颜色c = a[j],所有之前出现的颜色c的数目都可以与当前的j配对吗?或者,应该将之前所有的颜色c数目加到结果中? 或者,应该把当前的valid_counts[c]加到结果中? 或者,当遇到有效点j时,更新valid_counts数组为sum_color数组的当前值。然后,当处理后面的客栈i时,如果i的颜色是c,那么此时i可以和valid_counts[c]中的数目组成有效对,因为valid_counts[c]是到最近有效点j时的颜色c数目,所以i的位置在j之后,所以i和valid_counts中的那些客栈之间的区域包含j,而j是有效的,所以满足条件。 例如,假设在遇到有效点j时,记录sum_color到valid_counts中。之后,对于每个客栈i在j之后,只要i的颜色是c,那么它之前的valid_counts[c]中的数目即为可以与之配对的数量,因为这些客栈都位于j之前,而i在j之后,所以中间有j这个有效点。 同时,对于在j之后的有效点k,同样会更新valid_counts,此时i之后的客栈会使用新的valid_counts。 这种方法的具体步骤如下: 初始化sum_color数组为0,valid_counts数组为0,结果ans为0,latest_valid_pos为-1(表示还没有遇到有效点)。 遍历每个客栈i从0到n-1(假设索引从0开始): sum_color[a[i]] += 1 if b[i] <= p: // 当前i是有效点,更新valid_counts为sum_color的当前值(在i之前的数目) // 因为i是有效点,所以之后的客栈可以与i之前的颜色数目配对 // 但是,此时应该将valid_counts的各个颜色设置为sum_color,除了当前颜色可能? // 或者,valid_counts记录的是到上一个有效点之前的各个颜色数目? // 这里的逻辑可能需要更仔细的思考。 // 例如,当当前i是有效点时,那么对于所有在i之后出现的颜色c的客栈,可以与在i之前出现的颜色c的客栈配对,只要这些配对之间的区域包含i这个有效点。 // 所以,在每次遇到有效点时,需要将valid_counts数组更新为当前的sum_color数组的值。这样,当后面处理客栈j时,可以查询valid_counts中的颜色数目,并加到结果中。 // 但是,在i之后的客栈j,如果颜色c,那么其数目为sum_color[c] - valid_counts[c],这可能吗? // 或者,当遇到有效点时,将valid_counts记录为此时的sum_color数组,那么之后每当遇到颜色c的客栈时,该客栈可以与valid_counts[c]中的数目配对? // 例如,假设在遇到有效点i时,valid_counts被设置为当前sum_color。那么,当后面处理客栈j时,sum_color[c]是到j时的总数目,而valid_counts[c]是到i时的数目。那么,在i之后到j之间的颜色c的数目是sum_color[c] - valid_counts[c]。这部分数目是否可以与j之后的数目配对? // 可能我的思路有问题。正确的逻辑应该是,当遇到有效点i时,所有之前出现的颜色c的客栈,在i之前的位置,可以被之后的颜色c的客栈所配对,并且这两个客栈之间有i这个有效点。因此,每当遇到有效点i时,可以记录此时各颜色的数目,之后每当有颜色c的客栈j出现时,可以将j与valid_counts[c]中的数目配对。但这样是否正确? // 比如,假设i是有效点,在i之后有一个颜色c的客栈j。那么,j可以和所有在i之前(包括i)的颜色c的客栈配对吗?或者是否包括i之后、j之前的颜色c的客栈? // 比如,i是有效点,之后有一个颜色c的客栈k,在i和j之间。那么k和j之间的有效点可能是k自己(如果k的b<=p),或者i。所以,这可能被后面的有效点处理。 // 因此,当处理j时,如果j的颜色是c,并且存在一个有效点位于i<=k <=j,那么j可以和所有在有效点k之前的颜色c的客栈配对。但如何高效处理这种情况? // 所以,每次遇到有效点时,记录此时各颜色的数目,并且当处理j时,统计所有在最近的有效点之前的颜色数目。这样,j可以和这些数目中的颜色数目配对,因为这些数目对应的客栈位于有效点之前,因此j和这些客栈之间的区域包含有效点。 // 因此,正确的做法是,维护一个数组valid_counts,该数组保存的是在最近的有效点之前各个颜色的数目。每当遇到有效点时,将valid_counts更新为当前的sum_color数组的值。这样,在处理j时,如果j的颜色是c,则可以将valid_counts[c]加到结果中,因为这些数目对应的客栈位于有效点之前,而j在有效点之后,因此它们之间的区域包含有效点。 // 例如,假设sum_color[c]在有效点i时是m。之后,处理j时,如果j的颜色是c,那么valid_counts[c]是m,所以结果增加m。这表示j和i之前的m个c颜色的客栈配对。这些客栈的位置在i之前,而j在i之后,所以它们之间必有i作为有效点。 // 但如果在i和j之间还有另一个有效点k,比如i <k <j,那么在处理k时,valid_counts会被更新为sum_color的当前值,此时sum_color可能比i时更大。所以,当处理j时,valid_counts保存的是k时的sum_color,即所有到k时的数目。这样,j的配对数目是k时的数目,这包括了i到k之间的颜色数目吗? // 是的。假设i是第一个有效点,k是第二个有效点,且i <k <j。当处理到k时,valid_counts会被更新到sum_color的当前值,即到k时的数目。那么,当处理j时,如果j的颜色是c,并且j的b>p,那么valid_counts[c]是到k时的数目,j可以与之配对,因为k是有效点,位于k和j之间。如果j的b<=p,那么处理j时会更新valid_counts为当前的sum_color,即包括j之前的数目。 // 因此,这种方法可以正确统计所有满足条件的对数。因为每当j被处理时,如果有任何有效点在它之前(最近的那个),那么valid_counts保存的是该有效点时的各颜色数目。因此,当j的颜色是c时,valid_counts[c]即为在有效点之前的颜色数目,因此这些数目对应的客栈与j之间必然存在有效点。 // 所以,正确的做法是: // 初始化sum_color和valid_counts为全0,ans=0。 // 遍历每个客栈i: // sum_color[c] +=1 // if b[i] <=p: // ans += sum_color[c] -1 ? 或者如何处理? // 或者,此时需要将valid_counts更新为当前的sum_color的值,然后对于当前客栈i的颜色c,可以加上valid_counts_prev[c],其中valid_counts_prev是之前的valid_counts? // 这个思路可能有问题,可能需要更详细的分析。 // 或许应该这样:每次遇到有效点时,将valid_counts数组更新为当前的sum_color数组。这样,当处理后面的客栈j时,如果j的颜色是c,那么ans += valid_counts[c]。因为valid_counts[c]是在最近的有效点之前,颜色c的总数。这样,j可以和这些颜色c的客栈配对,因为它们之间的区域包含最近的有效点。 // 但如何处理当前客栈本身是否是有效点的情况? // 例如,当处理到客栈j,且j的b<=p时,此时需要更新valid_counts为当前的sum_color。然后,当处理后面的客栈时,他们可以访问这个valid_counts的值。而当前客栈j的颜色c,此时sum_color[c]是包括j的数目吗? // 是的。因为sum_color是在处理客栈j时增加的,所以当j是有效点时,sum_color已经包括了j的数目。此时,valid_counts会被设置为sum_color,也就是包括j的数目。那么,当后面的客栈k的颜色是c时,valid_counts[c]会包含j的数目。但是,j和k之间的有效点可能包括k自己,或者j? // 例如,假设k的b<=p,那么当处理k时,valid_counts会被更新为sum_color,其中sum_color包括k的数目。然后,当处理k后面的客栈m时,valid_counts[c]将包含k的数目。所以,m的颜色c的数目会被计算到,而中间的k是有效点。 // 这可能会有重复的问题吗? // 比如,假设j和k都是有效点,且j <k。当处理j时,valid_counts被更新到sum_color。此时,sum_color包括j的数目。当处理k时,sum_color又增加了k的数目,然后valid_counts被更新到sum_color。此时,当处理后面的m,颜色c,则valid_counts[c]是到k时的数目,包括j和k的数目。 // 这时候,如果m的颜色是c,那么ans += valid_counts[c],即到k时的数目。但是m可能位于k之后,那么k是有效点,所以所有在k之前的颜色c的客栈都可以与m配对,对吗? // 是的,因为k是有效点,位于那些客栈和m之间。所以,这样的配对是正确的。 // 那么,这样的方法是否正确?举个例子: // 输入样例: // 5 2 3 // 0 5 --> 客栈0,颜色0,消费5>3,无效 // 1 3 --> 客栈1,颜色1,消费3<=3,有效。此时sum_color[1]=1。valid_counts更新为sum_color的各颜色值。此时,valid_counts[0]=0, valid_counts[1]=1。ans此时是否增加? // 客栈0的颜色是0,但处理到客栈1时,它是否是有效点?是的。此时valid_counts被更新为sum_color的当前值(颜色0是1,因为前面处理过客栈0,颜色0的sum_color[0]是1?或者客栈0的索引是第一个,颜色0,sum_color[0]初始是0,处理到客栈0时,sum_color[0]增加到1。然后处理客栈1时,sum_color[1]增加到1。然后,因为客栈1的消费3<=3,所以将valid_counts设置为sum_color的当前值,即颜色0是1,颜色1是1。这时候,后面处理客栈时,可以开始累加。 // 例如,客栈2的颜色是0,消费2<=3。处理到客栈2时: // sum_color[0]变为2。然后,因为消费<=p,所以valid_counts被更新为sum_color的当前值,即颜色0是2,颜色1是1。此时,ans需要增加吗? // 比如,客栈2是颜色0,此时valid_counts[0]是之前的sum_color[0]吗?或者是在更新之前的值? // 可能我之前的思路需要调整。正确的步骤应该是: // 初始化sum_color为全0,valid_counts为全0,ans=0。 // 遍历每个客栈i: // 颜色c = a[i],消费b = b[i] // sum_color[c] +=1 // 如果b <=p: // ans += valid_counts[c] // 然后,将valid_counts数组更新为sum_color数组的值。或者,将valid_counts[c]的值设置为sum_color[c]? // // 因为,此时i是有效点,后面的客栈可以与i之前(包括i)的同颜色客栈配对? // // 例如,当i是有效点时,后面的客栈j如果颜色c,那么j可以与在i之前的颜色c的数目配对(包括i自己?) // // 因此,此时valid_counts应该记录到i时各颜色的总数。当后面处理j时,如果j的颜色是c,则ans += valid_counts[c]。这相当于i之后的所有同颜色客栈j可以配对i及之前的所有颜色c的数目? // // 但这样可能导致重复计算? // 例如,当i是有效点,更新valid_counts为sum_color。后面的j颜色c,ans += valid_counts[c]。这时,valid_counts[c]是sum_color[c](包括i),而j是后面的客栈,所以i和j之间有一个有效点i。这样是正确的。但是,当j也是一个有效点时,如何处理? // 例如,客栈i和j都是有效点,且i <j。那么,当处理i时,valid_counts被更新为sum_color,此时sum_color包括i的数目。当处理j时,如果j的颜色是c,ans会先加上valid_counts[c](即i时的数目),然后更新valid_counts为sum_color(此时包括i和j的数目)。这样,后面的客栈k会使用j时的数目。 // 这样是否正确? // 例如,假设i是颜色c,有效点,sum_color[c]是1。处理i时,ans加上valid_counts[c],此时valid_counts[c]可能为0?然后,valid_counts被更新为sum_color[c]=1。接着,j是颜色c,有效点。处理j时,sum_color[c]变为2。然后,ans += valid_counts[c](此时valid_counts是1),即ans增加1。然后,valid_counts被更新为2。此时,后面的客栈k颜色c,ans +=2。这样,总共有1(i时) +1(j时) + ... ? // 这时候,可能ans的计算方式有问题。正确的处理应该是,当遇到有效点i时,所有之前同颜色且在之前有效点之后的客栈都可以与i配对。或者,在遇到有效点时,可以统计之前同颜色的数目,并累加到ans中? // 可能我的之前的思路是错误的。正确的思路应该是在遍历客栈的时候,维护一个数组last[k],表示颜色k最后一次出现在有效点之后的客栈数目。或者,维护一个数组cnt[k],表示在当前有效区间内颜色k的客栈数目。当遇到新的有效点时,将cnt数组中的数目合并到总结果中? // 或者,我们可以将问题拆分为:每个有效点可以将前面的同颜色客栈与后面的同颜色客栈配对。例如,每个有效点i,其左边的颜色c的数目乘以右边的颜色c的数目,可以贡献到结果中。但这种方法可能无法处理多个有效点的情况,导致重复计算。 // 例如,客栈i和j都是有效点,且i<j。那么,左边的颜色c的数目到i时是A,右边的数目到i时为B。到j时,左边的数目是A+B,右边的数目是C。这样,如何处理? // 这似乎难以处理。可能另一个思路是,对于每个颜色c,维护一个列表positions,保存该颜色的所有客栈的位置。然后,对于每个颜色c的列表,遍历所有可能的i<j,并且存在某个k在i到j之间满足b[k]<=p。这可以通过预处理每个区间是否存在有效点,然后用双指针法统计。 // 例如,对于颜色c的列表pos_c,我们预处理每个pos_c[i],找到最小的k≥i,使得在pos_c[i]和 pos_c[j]之间存在有效点。然后,对于每个j,统计有多少i<j满足存在有效点在i到j之间。 // 如何高效处理这个? // 或许可以预处理一个数组min_valid,其中min_valid[i]表示从i开始,最近的满足b<=p的位置。这样,对于颜色c的两个位置i和j(i<j),如果min_valid[i] <=j,则这对是有效的。 // 那么,对于每个颜色c的列表pos_c,可以遍历每个j,并找到最大的i,使得min_valid[pos_c[i]] <= pos_c[j]。然后,i的取值范围是0到该最大i,此时这些i与j都是有效对。 // 这种方法的时间复杂度为每个颜色处理O(m log m),其中m是该颜色的客栈数目。总的时间复杂度为O(n log n),对于n=2e5来说可行。 // 例如,预处理每个位置的最近有效点: // 首先,预处理一个数组valid数组,其中valid[i]=True表示b[i]<=p。 // 然后,预处理一个数组prev_valid,其中prev_valid[i]表示i左边(包括i)最近的满足valid的位置。这可以通过一次遍历完成: // prev_valid[0] = 0 if valid[0] else -1 // 对于i>0: // prev_valid[i] = i if valid[i] else prev_valid[i-1] // 这样,prev_valid[i]保存的是i位置最近的左边的有效点。 // 另外,或者预处理一个数组next_valid[i],表示i位置之后的最早的有效点位置。这可能更复杂。 // 或者,预处理一个数组last_valid,表示到i位置为止,最近的有效点位置。例如,last_valid[i] = max(last_valid[i-1], i) if valid[i] else last_valid[i-1] // 这样,对于任意i,last_valid[i]是到i为止的最近有效点位置。这样,当处理j时,只要last_valid[j] >=i,则i到j之间存在有效点。 // 这样,预处理完last_valid数组后,对于颜色c的每个位置j,可以找到最大的i,使得i<j且存在有效点k在i到j之间。这等价于last_valid[j] >=i。因此,对于j来说,所有i<=last_valid[j]且i<j的同样颜色的客栈,都满足条件? // 不,因为last_valid[j]是到j为止的最近有效点的位置。假设i是颜色c的位置,且i<j。那么,如果last_valid[j] >=i,那么i到j之间存在有效点。因此,对于j来说,所有i在颜色c的列表中,且i<=last_valid[j]的i<j,都是有效对。 // 因此,对于颜色c的列表pos_c,每个j对应的i的数目等于在pos_c中,i<j且i <= last_valid[j]的数目。 // 因此,对于每个颜色c的列表pos_c,我们可以遍历每个j,然后在pos_c中找到最大的i,使得i <= last_valid[j]。然后,数目是i的数目(即数目是index+1,因为列表是顺序的)。 // 这可以通过二分查找实现。例如,pos_c是一个递增的列表。对于每个j的pos_j,在pos_c中找到最后一个i的pos_i <= last_valid[pos_j],并且i < j(pos_i < pos_j)。 // 例如,pos_c的列表是按顺序排列的。对于j的索引为k,那么在pos_c的前k个元素中,找到最大的pos_i <= last_valid[pos_j]。这可以用bisect_right来实现。 // 这样,每个颜色c的处理时间是O(m log m),其中m是该颜色的数目。总的时间复杂度是O(n + k*m log m)。假设每个颜色的m平均是n/k,总时间复杂度是O(n log (n/k)),对于k=50来说,是可行的。 // 现在,我需要确认这种方法的正确性。例如,样例输入: // 输入: // 5 2 3 // 客栈编号0~4: // 0: 0 5 (b=5>3 → invalid) // 1:1 3 (valid) // 2:0 2 (valid) // 3:1 4 (invalid) // 4:1 5 (invalid) // 预处理last_valid数组: // 初始last_valid[0] = -1(假设初始为-1,或者初始化为0是否有效?可能需要更仔细的处理) // 正确的预处理方法: // 初始化last_valid数组,其中last_valid[i]表示在0~i之间的最近的有效点位置。如果i是有效的,则last_valid[i]=i;否则,last_valid[i] = last_valid[i-1]。 // 所以: // i=0: valid? b=5>3 → no. last_valid[0] = -1. // i=1: b=3<=3 → yes. last_valid[1] =1. // i=2: b=2<=3 → yes. last_valid[2] =2. // i=3: b=4>3 → no. last_valid[3]=2. // i=4: b=5>3 → no. last_valid[4]=2. // 所以,last_valid数组为[-1,1,2,2,2]. // 对于颜色0的列表pos_c为 [0,2] // 处理颜色0的每个j: // j=0(位置0):由于颜色0,此时j的pos是0。last_valid[0]=-1 >=0?不成立。所以,无法找到i<j的i,数目0。 // j=2(位置2):last_valid[2]=2 >=i的pos_i的位置(即0和2的pos_i是否<=2?是的。i可以是0,因为0<=2,且i<j(0<2)?但j是pos_c中的第二个元素,索引为1。那么,在pos_c的前1个元素(索引0到0)中,找pos_i <= last_valid[2]=2。pos_i=0 <=2 → 是的。所以数目为1。即i=0,j=2 → 这是一个有效对。 // 颜色0的总贡献是1. // 颜色1的列表pos_c为 [1,3,4] // 处理每个j: // j=1(位置1): last_valid[1]=1。在pos_c中前面的元素(没有,因为j是第一个元素),数目0. // j=3(位置3): last_valid[3]=2. 找pos_i <=2且 <3。pos_c中的前面元素是1,位置1<=2 → 是。所以数目是1。即i=1,j=3 → 有效对?因为last_valid[3]=2 >=i=1。所以i=1到j=3之间的有效点是2的位置,所以是有效的。因此对数目贡献1. // j=4(位置4): last_valid[4]=2。找pos_i <=2且 <4。pos_c中的前面元素是1、3。3的位置是3>2,所以不符合。只有pos=1的i=1符合。所以数目是1。即i=1,j=4 → 有效对?因为i=1的位置<=2,所以有效。贡献1. // 所以颜色1的总贡献是0+1+1=2. // 总结果是1+2=3,与样例相符。这说明这种方法是正确的。 // 现在,如何实现这个方法? 步骤: 1. 预处理last_valid数组。 初始化last_valid数组,长度为n。last_valid[0] = 0 if b[0] <=p else -1. 然后,对于i从1到n-1: if b[i] <=p: last_valid[i] = i else: last_valid[i] = last_valid[i-1] 这样,对于每个i,last_valid[i]是0~i中最右边的有效点。如果该点存在的话,否则为-1. 2. 对每个颜色c,收集其所有客栈的位置,并排序。例如,建立一个字典color_pos,其中color_pos[c]是一个列表,按顺序保存该颜色各客栈的位置(0-based)。 3. 对每个颜色c的列表pos_list,遍历每个位置j_pos(即pos_list中的元素),然后找出在该列表中,位于j_pos之前的元素i_pos,且i_pos <= last_valid[j_pos]。这些i_pos对应的客栈i可以与j_pos对应的客栈j配对,因为i到j之间存在有效点last_valid[j_pos]。 这可以通过二分查找在pos_list中找到最大的i_pos <= last_valid[j_pos],并且i_pos <j_pos. 因为pos_list是按顺序递增的,所以可以用bisect_right来找。 例如,在pos_list中,对于j_pos,找到第一个大于last_valid[j_pos]的索引k。那么在k前面的元素都是<=last_valid[j_pos]. 但还需要确保这些元素的位置<j_pos. 但pos_list中的元素是递增的,所以对于某个j在pos_list中的索引为m,前面的元素都是< j_pos的。因此,所有在pos_list中0到m-1的位置i_pos都是<j_pos的。所以,问题转化为:在pos_list[0..m-1]中找到最大的i_pos <= last_valid[j_pos]. 这可以使用bisect_right的bisect模块。 例如,对于当前的j_pos,在pos_list中找到最大的i_pos <= last_valid[j_pos],并且i_pos <j_pos. 因为pos_list是递增的,所以可以这样处理: 在pos_list的前m-1个元素中进行二分查找,找到最大的数<=last_valid[j_pos]. 那么数目是bisect_right(pos_list, last_valid[j_pos], 0, m) → 返回插入的位置。例如,假设bisect_right返回k,则数目是k. 例如,pos_list=[1,3,4], j_pos=3的索引是1. 此时,last_valid[3]是2. 在pos_list的前1个元素(即[1])中找<=2。数目是1. 所以,当处理pos_list中的第m个元素j_pos时,数目等于bisect.bisect_right(pos_list, last_valid[j_pos], 0, m) → 返回k,数目是k。 这可以通过遍历每个颜色c的pos_list,然后对于每个j_pos在pos_list中的索引m,执行上述步骤。 时间复杂度:每个颜色c的pos_list长度为m,每个元素处理是O(log m). 所以总时间是O(n + k * m log m) = O(n log m) ≤ O(n log n). 对于n=2e5来说,是可行的。 现在,代码的大致步骤: - 读取n, k, p. - 读取n个客栈的a_i和b_i. - 预处理last_valid数组. - 建立color_pos字典,按颜色保存位置列表. - 对每个颜色c的pos_list: 遍历每个j_pos(索引m): m是当前元素的索引(从0到 len(pos_list)-1) last_valid_j = last_valid[j_pos] # 在pos_list的前m个元素中找到<=last_valid_j的数目 # 例如,在pos_list[0..m-1]中找<=last_valid_j的数目 count = bisect.bisect_right(pos_list, last_valid_j, 0, m) ans += count - 输出ans. 现在,样例的处理: color_pos[0] = [0,2] color_pos[1] = [1,3,4] 处理颜色0: j_pos=0,索引m=0。此时m=0,所以前m个元素是0→0到m-1是0到-1,没有元素。count=0. j_pos=2,索引m=1. 前m=1元素是[0]. 找<= last_valid[2]=2。bisect_right([0,2], 2, 0,1) → 在子数组[0]中找<=2,最大的元素是0,bisect_right返回1. 所以count=1. ans +=1. 颜色0总贡献1. 颜色1: j_pos=1,索引m=0 → 前0元素,count=0. j_pos=3,索引m=1 → 前1元素是[1]. last_valid[3]=2. 1 <=2 → bisect_right返回1. count=1. ans +=1 → ans=2. j_pos=4,索引m=2 → 前2元素是[1,3]. last_valid[4]=2. 找<=2的元素。bisect_right([1,3],2, 0,2) → 在子数组[1,3]的前两个元素中找<=2。3的前两个元素是1和3。在子数组[1,3]中,<=2的元素是1。bisect_right返回1. 所以count=1. ans +=1 → ans=3. 总ans=3,正确。 现在,如何处理输入的客栈顺序? 输入的客栈是按顺序给出的,编号是1到n吗?或者从0到n-1?需要注意索引是否正确。 在代码中,假设客栈的编号是0-based。例如,输入中的第i+1行对应客栈i的编号是i,i从0到n-1. 例如,输入样例中的客栈顺序是: 0号:0 5 → a=0, b=5 1号:1 3 → a=1, b=3 2号:0 2 → a=0, b=2 3号:1 4 → a=1, b=4 4号:1 5 → a=1, b=5 所以,last_valid数组的预处理: i=0: b=5>3 → last_valid[0]=-1(假设初始化为-1) i=1: b=3<=3 → last_valid[1]=1. i=2: b=2<=3 → last_valid[2]=2. i=3: b=4>3 → last_valid[3]=2. i=4: b=5>3 → last_valid[4]=2. 所以,color_pos的处理是正确的。 因此,这种方法是正确的。 现在,编写代码: 需要用到bisect模块。对于每个颜色c的pos_list,遍历每个元素j_pos,并找到在前面的元素中的数目。 代码的大致结构: import bisect n, k, p = map(int, input().split()) a = [] b = [] for _ in range(n): ai, bi = map(int, input().split()) a.append(ai) b.append(bi) # 预处理last_valid数组 last_valid = [-1]*n if b[0] <=p: last_valid[0] =0 else: last_valid[0] =-1 for i in range(1, n): if b[i] <=p: last_valid[i] =i else: last_valid[i] = last_valid[i-1] # 建立color_pos字典 from collections import defaultdict color_pos = defaultdict(list) for i in range(n): color_pos[a[i]].append(i) ans =0 for c in color_pos: pos_list = color_pos[c] for m in range(len(pos_list)): j_pos = pos_list[m] # j_pos是当前处理的客栈位置 # 找前面0~m-1的pos_list中的元素i_pos <= last_valid[j_pos] if m ==0: # 前面没有元素 continue # 在pos_list的0~m-1的范围内查找 # 使用bisect_right得到数目 count = bisect.bisect_right(pos_list, last_valid[j_pos], 0, m) ans += count print(ans) 测试样例: 输入: 5 2 3 0 5 1 3 0 2 1 4 1 5 color_pos[0] = [0,2] 处理j_pos=0(m=0) → 跳过。 j_pos=2(m=1): last_valid[2]=2. 在pos_list的0~0范围内找<=2 → pos_list[0]=0 <=2 → count=1. ans=1. color_pos[1] = [1,3,4] m=0 → 跳过. m=1 → j_pos=3. last_valid[3]=2. 在pos_list的0~0范围内(元素是1)找<=2 → 1<=2 → count=1. ans=2. m=2 → j_pos=4. last_valid[4]=2. 在pos_list的前两个元素[1,3]中找<=2. 即元素1和3。bisect_right([1,3],2,0,2) → 在子数组[1,3]中,bisect_right查找2的位置。由于子数组中的元素是1,3。2的位置是1,所以返回1。count=1. ans=3. 输出3,正确。 因此,代码是正确的。 现在,考虑其他测试用例。 比如,当所有客栈的消费都<=p时: n=2, k=1, p=5. 客栈0的a=0, b=3. 客栈1的a=0, b=4. 则last_valid数组为 [0,1]. color_pos[0]=[0,1]. 处理m=0 → 跳过. m=1 → j_pos=1. last_valid[j_pos]=1. 在pos_list的0~0范围内找<=1 → 0<=1 → count=1. ans=1. 所以,输出1,正确。 另一个情况,当两个同色客栈之间没有有效点: n=2, k=1, p=2. 客栈0的a=0, b=3>2 → last_valid[0]=-1. 客栈1的a=0, b=4>2 → last_valid[1]=-1. color_pos[0] = [0,1]. 处理m=0 → 跳过. m=1 → j_pos=1. last_valid[j_pos]=-1. 在pos_list的0~0范围(元素0)找<=-1 → 0<=-1不成立。count=0. 所以ans=0,正确。因为没有有效点。 因此,该算法是正确的。 综上所述,该问题可以通过预处理每个位置的最远有效点,然后对每个颜色统计符合条件的对数,使用二分查找来实现高效的计算。</think>要解决这个问题,我们需要找到满足以下条件的住宿方案数:两位游客住在同一色调的客栈,并且在他们之间(包括他们住的客栈)存在至少一家咖啡店的最低消费不超过给定值 \( p \)。 ### 方法思路 1. **预处理有效位置**:首先,我们遍历所有客栈,记录每个位置之前(包括自身)最近的消费不超过 \( p \) 的客栈位置。这样,对于任意两个客栈,我们只需检查它们之间是否存在这样的有效位置。 2. **按色调分类客栈**:将客栈按色调分类,记录每个色调的所有客栈位置。 3. **统计有效对数**:对于每个色调的每个客栈位置,使用二分查找快速统计其之前的所有符合条件的位置数。 ### 解决代码 ```python import bisect from collections import defaultdict n, k, p = map(int, input().split()) a = [] b = [] for _ in range(n): ai, bi = map(int, input().split()) a.append(ai) b.append(bi) # 预处理每个位置的最远有效点 last_valid = [-1] * n if b[0] <= p: last_valid[0] = 0 for i in range(1, n): if b[i] <= p: last_valid[i] = i else: last_valid[i] = last_valid[i - 1] # 按颜色分类,记录每个颜色的所有位置 color_pos = defaultdict(list) for idx in range(n): color_pos[a[idx]].append(idx) ans = 0 # 对每个颜色处理 for c in color_pos: positions = color_pos[c] for m in range(len(positions)): j_pos = positions[m] # 当前有效点的位置 lv = last_valid[j_pos] if lv == -1: continue # 没有有效点,无法配对 # 在positions的前m个元素中找到<=lv的数量 # 使用bisect_right在positions[0..m-1]中找 count = bisect.bisect_right(positions, lv, 0, m) ans += count print(ans) ``` ### 代码解释 1. **输入处理**:读取输入的客栈数量 \( n \)、色调数 \( k \) 和最高可接受消费 \( p \),然后读取每个客栈的色调和最低消费。 2. **预处理有效位置**:`last_valid` 数组记录每个位置之前(包括自身)最近的消费不超过 \( p \) 的客栈位置。 3. **按色调分类**:使用字典 `color_pos` 将每个色调的客栈位置记录下来。 4. **统计有效对数**:对于每个色调的每个位置,使用二分查找快速找到其之前的所有有效位置,并统计这些位置的数量,累加到结果中。 该方法的时间复杂度为 \( O(n \log n) \),适用于题目给定的数据范围。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值