P8600 [蓝桥杯 2013 省 B] 连号区间数 and CF526F
问题转化
很容易就能把原问题转化成:
求满足 Max-Min = r-l的区间个数
暴力解法
根据上面得到的性质,我们可以暴力枚举区间,来判断当前区间是否满足性质
#include <iostream> #include <stdio.h> #include <algorithm> #include <string> #include <cmath> #include <string.h> #define R(x) x = read() #define For(i, j, n) for (int i = j; i <= n; ++i) using namespace std; 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; } const int N = 50005; typedef long long LL; int n, a[N]; int Log[N]; int Max[N][17], Min[N][17]; //2^16 = 65536 void init() { memset(Min, 0x3f, sizeof(Min)); Log[0] = -1; for(int i = 1; i <= n; i++) Log[i] = Log[i>>1] + 1, Min[i][0] = Max[i][0] = a[i]; for(int j = 1; j <= 16; j++) { for(int i = 1; i + (1 << j - 1) <= n; i++) Max[i][j] = max(Max[i][j - 1], Max[i + (1 << j - 1)][j -1]), Min[i][j] = min(Min[i][j - 1], Min[i + (1 << j - 1)][j - 1]); } } int query(int l, int r, int t) { int len = r - l + 1; if(t) return max(Max[l][Log[len]], Max[r - (1 << Log[len]) + 1][Log[len]]); else return min(Min[l][Log[len]], Min[r - (1 << Log[len]) + 1][Log[len]]); } int main() { R(n); For(i, 1, n) R(a[i]); init(); LL ans = 0ll; for(int len = 1; len <= n; len++) for(int i = 1; i + len - 1 <= n; i++) { int j = i + len - 1; ans += (query(i, j, 1) - query(i, j, 0) == len - 1); } printf("%lld\n", ans); return 0; }
时间复杂度O(n^2),无法在750ms的时间限制下通过本题
分治解
#include <iostream> #include <stdio.h> #include <algorithm> #include <string> #include <cmath> #define R(x) x = read() #define For(i, j, n) for(int i = j ; i <= n ; ++i) using namespace std; 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; } const int N = 5e4 + 5, M = 1e5 + 5; typedef long long LL; int n, a[N]; LL ans; int cnt[M<<1]; //桶 int Min[N], Max[N]; void solve(int l, int r) // 当前处理l~r { if(l == r) { ans++; return; } int mid = (l + r) >> 1; // 处理l,r横跨两边的 Min[mid] = Max[mid] = a[mid]; // i <= mid, Min,Max表示后缀最值 for(int i = mid - 1; i >= l; i--) { Min[i] = min(a[i], Min[i + 1]); Max[i] = max(a[i], Max[i + 1]); } Min[mid + 1] = Max[mid + 1] = a[mid + 1]; // i > mid, Min,Max表示前缀最值 for(int i = mid + 2; i <= r; i++) { Min[i] = min(a[i], Min[i - 1]); Max[i] = max(a[i], Max[i - 1]); } // ----------------最大值在左边 int Pr1 = mid + 1, Pr2 = mid + 1; for(int Pl = mid; Pl >= l; Pl--) { // 最大值在左边,最小值在右边 while(Pr1 <= r && Max[Pr1] < Max[Pl]) cnt[Min[Pr1] + Pr1 + M]++, Pr1++; //注意这里可以取等号 while(Pr2 < Pr1 && Min[Pr2] > Min[Pl]) cnt[Min[Pr2] + Pr2 + M]--, Pr2++;// 这里不能取等号 // 最后有解的区间是 [Pr2, Pr1 - 1] ans += cnt[Max[Pl] + Pl + M]; // 判断最值均在左边的情况是否成立 int TmpR = Max[Pl] - Min[Pl] + Pl; if(TmpR > mid && TmpR < Pr2) ans++; } // ----------------最大值在左边 for(int i = Pr2; i < Pr1; i++) cnt[Min[i] + i + M] = 0; // 清空影响 // ----------------最大值在右边 int Pl1 = mid, Pl2 = mid; for(int Pr = mid + 1; Pr <= r; Pr++) { // 最大值在右边,最小值在左边 while(Pl1 >= l && Max[Pl1] < Max[Pr]) cnt[Min[Pl1] - Pl1 + M]++, Pl1--; while(Pl2 > Pl1 && Min[Pl2] > Min[Pr]) cnt[Min[Pl2] - Pl2 + M]--, Pl2--; ans += cnt[Max[Pr] - Pr + M]; // 判断最值均在右边的情况是否成立 int TmpL = Min[Pr] - Max[Pr] + Pr; if(TmpL <= mid && TmpL > Pl2) ans++; } // ----------------最大值在右边 for(int i = Pl2; i > Pl1; i--) cnt[Min[i] - i + M] = 0; // 清空影响 solve(l, mid);solve(mid + 1, r); } int main() { R(n); For(i, 1, n) R(a[i]); solve(1, n); printf("%lld\n", ans); return 0; }
时间复杂度O(nlogn),可以通过
注释已经比较详细,需要额外关注的是:
// 最大值在左边,最小值在右边 while(Pr1 <= r && Max[Pr1] < Max[Pl]) cnt[Min[Pr1] + Pr1 + M]++, Pr1++; //注意这里可以取等号 while(Pr2 < Pr1 && Min[Pr2] > Min[Pl]) cnt[Min[Pr2] + Pr2 + M]--, Pr2++;// 这里不能取等号 // 最后有解的区间是 [Pr2, Pr1 - 1]
从while循环的边界条件中,我们不难看出,最后对应的有解区间是[Pr2,Pr1-1]
1.为什么第一个while是Pr1<=r
因为Max[r]也有可能满足条件,不能落下
2.为什么第二个while不能取等号
因为最后有解的区间是[Pr2,Pr1-1],Pr2<Pr1也就是Pr2<=Pr1-1,就像队列一样,这里的双指针包围区间至少有一个元素时,才能进行“出队”操作,否则,队列本来就已经为空还出队,就会造成指针(下标)越界。
这里有很多相似的变量名,注意不要写错了。
线段树+单调栈
#include <iostream> #include <stdio.h> #include <algorithm> #include <string> #include <cmath> #define R(x) x = read() #define For(i, j, n) for(int i = j ; i <= n ; ++i) #define lSon u<<1 #define rSon u<<1|1 using namespace std; typedef long long LL; 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; } const int N = 50005; LL ans; int n, a[N]; struct treeNode{ int l, r, minVal, cnt, addTag; // minVal存储max-min+l }segTree[N << 2]; void pushUp(int u) { segTree[u].minVal = min(segTree[lSon].minVal, segTree[rSon].minVal); int a = segTree[u].minVal==segTree[lSon].minVal?segTree[lSon].cnt:0, b = segTree[u].minVal==segTree[rSon].minVal?segTree[rSon].cnt:0; segTree[u].cnt = a + b; } void build(int u, int l, int r) { segTree[u].addTag = 0; segTree[u].l = l; segTree[u].r = r; if(l == r) { segTree[u] = {l, r, l, 1, 0}; return; } int mid = l + r >> 1; build(lSon, l, mid); build(rSon, mid + 1, r); pushUp(u); } void change(int u, int v) { segTree[u].minVal += v; segTree[u].addTag += v; } void pushDown(int u) { if(segTree[u].addTag == 0) return; change(lSon, segTree[u].addTag); change(rSon, segTree[u].addTag); segTree[u].addTag = 0; } void updateTree(int u, int l, int r, int val) { if(segTree[u].l >= l && segTree[u].r <= r) { change(u, val); return; } pushDown(u); int mid = segTree[u].l + segTree[u].r >> 1; if(l <= mid) updateTree(lSon, l, r, val); if(r > mid) updateTree(rSon, l, r, val); pushUp(u); } int maxStk[N], maxTop, minStk[N], minTop; //以maxStk为例,maxStk[maxTop]维护的是:[maxStk[maxTop - 1] + 1, maxStk[maxTop]]内的区间max //单调栈和单调队列一样,存储的都是下标 void solve() { build(1, 1, n); for(int i = 1; i <= n; i++) { //printf("%d\n", i); //维护maxStk while(maxTop && a[i] > a[maxStk[maxTop]]) { updateTree(1, maxStk[maxTop - 1] + 1, maxStk[maxTop], a[i] - a[maxStk[maxTop]]); //如果maxTop=1,栈内只有一个元素,那么maxStk[maxTop - 1] + 1恰好是1,正好就改变了整个区间的max值,这是符合预期的,因此不需要特判 //printf("case 1:updateTree(1, %d, %d, %d)\n", maxStk[maxTop - 1] + 1, maxStk[maxTop], a[i] - a[maxStk[maxTop]]); maxTop--; } //维护minStk while(minTop && a[i] < a[minStk[minTop]]) { updateTree(1, minStk[minTop - 1] + 1, minStk[minTop], a[minStk[minTop]] - a[i]); //printf("case 2:updateTree(1, %d, %d, %d)\n", minStk[minTop - 1] + 1, minStk[minTop], a[minStk[minTop]] - a[i]); minTop--; } ans += segTree[1].cnt; maxStk[++maxTop] = minStk[++minTop] = i; } } int main() { R(n); For(i, 1, n) R(a[i]); solve(); printf("%lld\n", ans); return 0; }
因为以前没有用过单调栈,一直以为它和单调队列的实际含义差不多,导致在理解这个写法的时候卡了很久。
要注意:
单调栈和单调队列一样存储的是下标,所以这里要千万小心,别写错了:
for(int i = 1; i <= n; i++) { //printf("%d\n", i); //维护maxStk while(maxTop && a[i] > a[maxStk[maxTop]]) { updateTree(1, maxStk[maxTop - 1] + 1, maxStk[maxTop], a[i] - a[maxStk[maxTop]]); //如果maxTop=1,栈内只有一个元素,那么maxStk[maxTop - 1] + 1恰好是1,正好就改变了整个区间的max值,这是符合预期的,因此不需要特判 //printf("case 1:updateTree(1, %d, %d, %d)\n", maxStk[maxTop - 1] + 1, maxStk[maxTop], a[i] - a[maxStk[maxTop]]); maxTop--; } //维护minStk while(minTop && a[i] < a[minStk[minTop]]) { updateTree(1, minStk[minTop - 1] + 1, minStk[minTop], a[minStk[minTop]] - a[i]); //printf("case 2:updateTree(1, %d, %d, %d)\n", minStk[minTop - 1] + 1, minStk[minTop], a[minStk[minTop]] - a[i]); minTop--; } ans += segTree[1].cnt; maxStk[++maxTop] = minStk[++minTop] = i; }