出处: NOIp2011提高组 D1T2
主要算法:递推
难度:4.3
思路分析:
先打了一个n^2暴力拿到了60分。简简单单的一个ST表RMQ预处理,然后n^2枚举客栈求区间最小值判一下是否<=p就可以了,还是很简单的。但是刚开始RMQ打错了。RMQ的边界条件还是非常坑的!
说一说正解吧,正解跟RMQ压根没关系。
我们把思路反过来(其实这是我一开始的思路):考虑每个满足<=p的客栈,在它两边的同种颜色的客栈都可以进行累计。那我们是不是可以枚举<=p的客栈往两边扩散呢?显然不行,因为这样子重复的选择情况太多了。如何避免重复呢?我们选择枚举右边的那个客栈,也就是区间的右边界r。找到在位置<=r的离他最近的那一个消费<=p的客栈v,这样位置小于v的同种颜色的客栈就全部可以与r匹配累计答案了。
于是现在出现了两个问题:
(一):如何求出对于每一个右边界i,离他最近的消费<=p的客栈的位置last[i]?注意不能枚举,因为n^2就爆了。所以必须要一个O(1)的算法。这个还是很容易的,只需要递推一下就行了。只有会去想都能够想到转移方程:如果当前这个客栈的消费就<=p,那么last[i] = i,否则选择继承,last[i] = last[i-1]
(二):如何统计出在这个喝咖啡的客栈左侧的同种颜色的客栈个数?在读入时统计一个color[i][j]表示颜色为i的第j个客栈的位置。这个用二维数组不太方便,但用个vector是再好不过了的。这样,我们在统计选定的客栈左侧有多少个满足条件的合法客栈时,就可以二分——二分出颜色相同的在它左边的个数,答案累加。
复杂度O(n log n)
代码注意点:
有一个点非常坑,而且没注意到就是10分。那就是这两个人不会住到同一个客栈离去,这样的话如果二分出来的位置与右边界相同,就必须-1.
/*By QiXingzhi*/ #include <cstdio> #include <vector> #define N (200010) #define r read() #define INF (0x3f3f3f3f) #define Max(a,b) (((a)>(b)) ? (a) : (b)) #define Min(a,b) (((a)<(b)) ? (a) : (b)) typedef long long ll; using namespace std; inline int read(){ int x = 0; int w = 1; register int c = getchar(); while(c ^ '-' && (c < '0' || c > '9')) c = getchar(); if(c == '-') w = -1, c = getchar(); while(c >= '0' && c <= '9') x = (x << 3) +(x << 1) + c - '0', c = getchar(); return x * w; } int n,m,k,p,ans,x,L,R,Mid,_a; int color[N],low[N],last[N]; vector <int> c[N]; int main(){ // freopen(".in","r",stdin); n=r,k=r,p=r; for(int i = 1; i <= n; ++i){ color[i] = r; low[i] = r; if(low[i] <= p) last[i] = i; else last[i] = last[i-1]; c[color[i]].push_back(i); } for(int i = 2; i <= n; ++i){ if(!last[i]) continue; x = last[i]; _a = -1; L = 0, R = c[color[i]].size()-1; while(L <= R){ Mid = (L + R) >> 1; if(c[color[i]][Mid] <= x){ _a = Mid; L = Mid + 1; }else{ R = Mid - 1; } } if(c[color[i]][_a] == i) --_a; if(_a != -1){ ans += _a+1; } } printf("%d",ans); return 0; }