Codeforces 526F Pudding Monsters - CDQ分治 - 桶排序

In this problem you will meet the simplified model of game Pudding Monsters.

An important process in developing any game is creating levels. A game field in Pudding Monsters is an n × n rectangular grid, n of its cells contain monsters and some other cells contain game objects. The gameplay is about moving the monsters around the field. When two monsters are touching each other, they glue together into a single big one (as they are from pudding, remember?).

Statistics showed that the most interesting maps appear if initially each row and each column contains exactly one monster and the rest of map specifics is set up by the correct positioning of the other game objects.

A technique that's widely used to make the development process more efficient is reusing the available resources. For example, if there is a large n × n map, you can choose in it a smaller k × k square part, containing exactly k monsters and suggest it as a simplified version of the original map.

You wonder how many ways there are to choose in the initial map a k × k (1 ≤ k ≤ n) square fragment, containing exactly k pudding monsters. Calculate this number.

Input

The first line contains a single integer n (1 ≤ n ≤ 3 × 105) — the size of the initial field.

Next n lines contain the coordinates of the cells initially containing monsters. The i-th of the next lines contains two numbers ri, ci(1 ≤ ri, ci ≤ n) — the row number and the column number of the cell that initially contains the i-th monster.

It is guaranteed that all ri are distinct numbers and all ci are distinct numbers.

Output

Print the number of distinct square fragments of the original field that can form a new map.

Examples
input
5
1 1
4 3
3 2
2 4
5 5
output
10

  题目大意 给定平面上有n个点,每行每列上只有一个点,问总共有多少个边长为k的矩形框住了k个点。

  刚开学,好久(也就两周)都没搞OI,差点不知OI为何物了。qaq。有人把这道题改题面拿给我们考试,然后考试时没想出正解,暴力分段90分。。于是班上出现了一群优秀的魔(膜)法师。

  显然,可以把它转化成序列上的问题(序列就是a[i]表示第i列上的点的纵坐标):有多少个长度为k的区间使得这一段的最大值和最小值之差等于k - 1。

  然后上CDQ分治进行瞎搞。记当前分治区间为[l, r],分治中心为mid,统计经过分治中心的子区间,分四种情况讨论:

  1)子区间的最大值和最小值同在左侧(相对于分治中心)

  2)子区间的最小值在左侧,最大值在右侧

  3)子区间的最大值和最小值同在右侧

  4)子区间的最大值在左侧,最小值在右侧

  由于情况1,3和2,4的做法类似,所以只考虑情况1,2

  情况1:

    记录左侧每个位置到分治中心的最大值和最小值。

    枚举子区间的左端点,可以根据记录的数据计算出右端点,如果合法(在分治中心右侧,并且最大值和最小值满足在左侧)就将答案加1.

  情况2:

    从分治中心向左枚举左端点i,考虑先使最值的条件合法。

    考虑到前后缀最大值和最小值都有不增或不减的单调性。所以设置两个"指针",r1和r2。

    r1是第一个使最大值在右侧的位置,r2是第一个使最小值不在左边的位置。那么左端点为i,右端点在整数区间[r1, r2)内的子区间都满足最值的限制。

    现在考虑如何统计答案。临时约定max[l, r]表示a[l],a[l + 1], ..., a[r]的最大值,同理定义min[l, r]。

    显然

    移一下项得到

    所以在挪动"指针"的时候把右边的一坨东东扔进某个桶里面就好了。挪动完成后根据当前枚举的左端点i,首先判断是否合法(比如什么r1大于等于r2的时候就continue就好了,如果合法的话就加上对应桶里的计数就好了。

    这种情况搞定了之后,不要忘记清空桶,由于桶可能很大,所以不要memset或者fill,for数组a就好了。

Code

  1 /**
  2  * Codeforces
  3  * Problem#526F
  4  * Accepted
  5  * Time: 140ms
  6  * Memory: 8200k
  7  */
  8 #include <iostream>
  9 #include <fstream>
 10 #include <sstream>
 11 #include <cstdio>
 12 #include <cstdlib>
 13 #include <cstring>
 14 #include <cmath>
 15 #include <ctime>
 16 #include <cctype>
 17 #include <algorithm>
 18 #include <set>
 19 #include <map>
 20 #include <vector>
 21 #include <queue>
 22 #include <stack>
 23 #include <bitset>
 24 #ifndef WIN32
 25 #define Auto "%lld"
 26 #else
 27 #define Auto "%I64d"
 28 #endif
 29 using namespace std;
 30 typedef bool boolean;
 31 #define clra(a) memset(a, false, sizeof(a))
 32 const signed int inf = ((~0u) >> 1);
 33 #define smin(a, b) a = min(a, b)
 34 #define smax(a, b) a = max(a, b)
 35 #define LL long long
 36 
 37 const int N = 3e5;
 38 
 39 int n;
 40 int *arr;
 41 int *pmin, *pmax, *rmin, *rmax;
 42 int bucket[(N << 1) + 5];
 43 
 44 inline void init() {
 45     scanf("%d", &n);
 46     arr = new int[(n + 1)];
 47     pmin = new int[(n + 1)];
 48     pmax = new int[(n + 1)];
 49     rmin = new int[(n + 1)];
 50     rmax = new int[(n + 1)];
 51     for(int i = 1, x, y; i <= n; i++) {
 52         scanf("%d%d", &x, &y);
 53         arr[x] = y;
 54     }
 55 }
 56 
 57 LL CDQDividing(int l, int r) {
 58     if(l == r)    return 1;
 59     
 60     int mid = (l + r) >> 1;
 61     LL rt = 0;
 62     pmin[mid] = arr[mid], pmax[mid] = arr[mid], rmin[mid + 1] = arr[mid + 1], rmax[mid + 1] = arr[mid + 1];
 63     for(int i = mid - 1; i >= l; i--)
 64         pmin[i] = min(pmin[i + 1], arr[i]), pmax[i] = max(pmax[i + 1], arr[i]);
 65     for(int i = mid + 2; i <= r; i++)
 66         rmin[i] = min(rmin[i - 1], arr[i]), rmax[i] = max(rmax[i - 1], arr[i]);
 67         
 68     for(int i = l, rg; i <= mid; i++) {
 69         rg = i + pmax[i] - pmin[i];
 70         rt += rg > mid && rg <= r && rmax[rg] < pmax[i] && rmin[rg] > pmin[i];
 71     }
 72     for(int i = mid + 1, lf; i <= r; i++) {
 73         lf = i - rmax[i] + rmin[i];
 74         rt += lf <= mid && lf >= l && pmax[lf] < rmax[i] && pmin[lf] > rmin[i];
 75     }
 76     for(int i = mid, r1 = mid + 1, r2 = mid + 1; i >= l && r1 <= r; i--) {    // The min num is on the left.
 77         while(r2 <= r && rmin[r2] > pmin[i])    bucket[r2 - rmax[r2] + N]++, r2++;
 78         while(r1 <= r && rmax[r1] < pmax[i])    bucket[r1 - rmax[r1] + N]--, r1++;
 79         if(r1 < r2)    rt += bucket[i - pmin[i] + N];
 80     }
 81     for(int i = mid + 1; i <= r; i++)
 82         bucket[i - rmax[i] + N] = 0;
 83 //    cout << rt << endl;
 84     for(int i = mid + 1, l1 = mid, l2 = mid; i <= r && l2 >= l; i++) {    // The min num is on the right.
 85         while(l1 >= l && pmin[l1] > rmin[i])    bucket[l1 + pmax[l1]]++, l1--;
 86         while(l2 >= l && pmax[l2] < rmax[i])    bucket[l2 + pmax[l2]]--, l2--;
 87         if(l1 < l2)    rt += bucket[i + rmin[i]];
 88     }
 89     for(int i = l; i <= mid; i++)
 90         bucket[i + pmax[i]] = 0;
 91     
 92     return rt + CDQDividing(l, mid) + CDQDividing(mid + 1, r);
 93 }
 94 
 95 inline void solve() {
 96     printf(Auto, CDQDividing(1, n));
 97 }
 98 
 99 int main() {
100     init();
101     solve();
102     return 0;
103 }

转载于:https://www.cnblogs.com/yyf0309/p/7498514.html

CodeForces - 616D是一个关于找到一个序列中最长的第k好子段的起始位置和结束位置的问题。给定一个长度为n的序列和一个整数k,需要找到一个子段,该子段中不超过k个不同的数字。题目要求输出这个序列最长的第k好子段的起始位置和终止位置。 解决这个问题的方法有两种。第一种方法是使用尺取算法,通过维护一个滑动窗口来记录\[l,r\]中不同数的个数。每次如果这个数小于k,就将r向右移动一位;如果已经大于k,则将l向右移动一位,直到个数不大于k。每次更新完r之后,判断r-l+1是否比已有答案更优来更新答案。这种方法的时间复杂度为O(n)。 第二种方法是使用枚举r和双指针的方法。通过维护一个最小的l,满足\[l,r\]最多只有k种数。使用一个map来判断数的种类。遍历序列,如果当前数字在map中不存在,则将种类数sum加一;如果sum大于k,则将l向右移动一位,直到sum不大于k。每次更新完r之后,判断i-l+1是否大于等于y-x+1来更新答案。这种方法的时间复杂度为O(n)。 以上是两种解决CodeForces - 616D问题的方法。具体的代码实现可以参考引用\[1\]和引用\[2\]中的代码。 #### 引用[.reference_title] - *1* [CodeForces 616 D. Longest k-Good Segment(尺取)](https://blog.csdn.net/V5ZSQ/article/details/50750827)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Codeforces616 D. Longest k-Good Segment(双指针+map)](https://blog.csdn.net/weixin_44178736/article/details/114328999)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值