您的位置:首页 > 其它

NOIP 2011 Senior 2 - 选择客栈

2017-07-05 15:35 447 查看
选择客栈

总时间限制: 1000ms 内存限制: 65535kB

描述

丽江河边有 n 家很有特色的客栈,客栈按照其位置顺序从 1 到n 编号。每家客栈都按照某一种色调进行装饰(总共 k 种,用整数 0 ~ k-1 表示) ,且每家客栈都设有一家咖啡店,每家咖啡店均有各自的最低消费。

两位游客一起去丽江旅游,他们喜欢相同的色调,又想尝试两个不同的客栈,因此决定分别住在色调相同的两家客栈中。晚上,他们打算选择一家咖啡店喝咖啡,要求咖啡店位于两人住的两家客栈之间(包括他们住的客栈) ,且咖啡店的最低消费不超过 p。

他们想知道总共有多少种选择住宿的方案,保证晚上可以找到一家最低消费不超过 p 元的咖啡店小聚。

输入

第一行三个整数 n,k,p,每两个整数之间用一个空格隔开,分别表示客栈的个数,色调的数目和能接受的最低消费的最高值;

接下来的 n行,第 i+1 行两个整数,之间用一个空格隔开,分别表示 i 号客栈的装饰色调和 i 号客栈的咖啡店的最低消费。

输出

输出只有一行,一个整数,表示可选的住宿方案的总数。

样例输入

5 2 3

0 5

1 3

0 2

1 4

1 5

样例输出

3

提示

2 人要住同样色调的客栈,所有可选的住宿方案包括:住客栈①③,②④,②⑤,④⑤,

但是若选择住 4、5 号客栈的话,4、5 号客栈之间的咖啡店的最低消费是 4,而两人能承受的最低消费是 3 元,所以不满足要求。因此只有前 3 种方案可选。

数据范围

对于 30%的数据,有 n ≤ 100;

对于 50%的数据,有 n ≤ 1,000;

对于 100%的数据,有 2 ≤ n≤ 200,000,0 < k ≤ 50,0 ≤ p ≤ 100, 0 ≤ 最低消费 ≤ 100。

思路 1

不管怎么说,第一个思路还是枚举。直接枚举选择的客栈,时间复杂度为O(n^2)。但是每次获取一个区间的最小值的时间复杂度为O(n),所以整个算法的时间复杂度为O(n^3)。

预计得分:30

思路 2

既然要取最小值,就想到了RMQ算法,初始化的时间复杂度为O(n log n),整个算法的时间复杂度为O(n^2)。

预计得分:50

思路 3

根据普及组的经验,可以将客栈色调的总数k运用起来,将所有同一色调的客栈分成一组。这样的时间复杂度虽然仍是O(n^2),但是可能会多过一两个点。

思路 4

受思路三的启发,可以用相同的思路提取一些公式。

假设某一种色调的客栈为A0,A1, …, An(这里的n指这种色调的客栈的总数)。

可以发现,若客栈Ax
4000
,Ax+1之间的最小值小于等于p,那么以这两个客栈为分界线,左边右边任选一个(包括Ax,Ax+1),是都能满足条件的,这样的方案总数为x*(i-x)。如果我们枚举每一个客栈之间的间隔,那么总的时间复杂度可以为O(n),在使用RMQ后为O(n log n),200000的数据应该能过了。

接下来考虑细节。因为有可能重复计算,所以我们可以考虑以下情况:

A1 A2 A3 A4 A5

满↑足 满↑足

在A1,A2之间满足条件,则可以有1∗4种方案。在A3,A4之间也满足条件,单独计算的话有3*2种方案,但是A1在左边的情况已经计算过了,所以我们可以保存上次计算的位置,然后减去重复的就可以了。对这个例子而言,先计算A1∗(A2−A5),然后计算 (A2−A3)∗(A4−A5),不计算A1∗(A4−A5)的重复情况。

long long ans = 0;
for (int i = 0; i < k; i++)
{
int size = styleSet[i].size(); //当前色调客栈个数
int right = size - 1;
int lastLeft = 0; //用于记录上一次计算了的位置以去掉重复计算
for (int j = 0; j < size - 1; j++)
{
if (rmq.query(styleSet[i][j], styleSet[i][j + 1]) <= p) //如果这两个客栈之间满足最小花费小于p
{
ans += (j - lastLeft + 1) * (right - j); //去重
lastLeft = j + 1; //记录
}
}
}
cout << ans;


得分:100

思路 5 动态维护O(1)数据

从1到n枚举所有的客栈i,然后计算从1到客栈i-1中有符合条件的客栈的个数,其和便是答案。这种思想正是枚举的思想,但是如果能把这种思想中的某些步骤做到时间复杂度为O(1),那么问题就解决了。往往这么做的效率很高,因此这种思路是一种非常重要的思路,而实现这种思路也是一种非常重要的能力。

首先,让我们考虑一下咖啡店的选址。毫无疑问,以我们刚刚的思路来做的话咖啡店是越往右越好的。这一点很好维护,只需要用一个bestPos变量来更新就可以了。

然后,我们可以考虑如何计算1到i-1号客栈中符合条件的客栈。由于要求颜色相同,因此我们可以用一个数组来保存计算到第i号时各个色调的客栈的个数。

然而,我们需要的是1到i-1号中符合条件的,所以得想办法维护它。我们注意到,每当bestPos更新时,都可以保证在bestPos之前的所有同色客栈可用……这样推算下去,我们一共需要4个辅助数据就能实现O(1)查询(自己想确实不怎么好想,但是看代码应该很好理解):

int bestPos = 0; //咖啡店应尽量靠右,保存咖啡店的最佳地址
int times[maxn] = { 0 }; //保存同色客栈出现的次数
int lastAppear[maxn] = { 0 }; //保存某种颜色客栈上一次出现的位置
int availableTimes[maxn] = { 0 }; //保存某种颜色客栈可形成一对的个数


最后计算答案的部分:

long long ans = 0;
for (int i = 1; i <= n; i++)
{
int style = readIn();
int cost = readIn();
if (cost <= p)
{
bestPos = i; //维护咖啡店位置
}

if (bestPos >= lastAppear[style]) //若bestPos在上一次这个颜色的客栈还要后面一点,就可以保证有times[style]个客栈可配对(此时times还没有更新)
availableTimes[style] = times[style];

lastAppear[style] = i; //维护上一次出现的位置
times[style]++; //维护出现的次数
ans += availableTimes[style]; //计算答案
}


惊讶的发现,这种算法不仅时间复杂度是绝对的O(n),而且空间复杂度也和n无关,只与k有关。因此,这种动态维护O(1)数据的方法的威力不容小觑。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: