Codeforces 526 F Pudding Monsters题解(线段树+单调栈)

本文探讨CF526F题目的优化解决方案,该问题要求在大规模矩阵中寻找特定配置的数量。通过巧妙转换,问题变为寻找序列中满足特定条件的子区间数目。文章介绍了一种在O(nlogn)时间复杂度内解决此问题的方法,涉及线段树和单调栈的使用。

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

题目:CF526F.
题目大意:给定一个 n ∗ n n*n nn的矩阵,其中 n n n个坐标有人且每一行和每一列均只有一人,求 k ∗ k k*k kk的矩阵中只有 k k k个人的矩阵的数量( k k k可以使任何数,不是常数).
1 ≤ n ≤ 3 ∗ 1 0 5 1\leq n\leq 3*10^5 1n3105.

首先容易把问题转化为求一个序列 a a a中,有多少个子区间 [ l , r ] [l,r] [l,r]满足 max ⁡ i = l r { a i } − min ⁡ i = l r { a i } = r − l \max_{i=l}^{r}\{a_i\}-\min_{i=l}^{r}\{a_i\}=r-l maxi=lr{ai}mini=lr{ai}=rl.

这个问题很容易在 O ( n 2 ) O(n^2) O(n2)的时间复杂度内解决,可是题目的要求明显是 O ( n log ⁡ n ) O(n\log n) O(nlogn)及更优的复杂度.

考虑改变一下转化一下条件式子:
m x = max ⁡ i = l r { a i } , m n = min ⁡ i = l r { a i } m x − m n = r − l ⇔ m x − m n − r + l = 0 mx=\max_{i=l}^{r}\{a_i\},mn=\min_{i=l}^{r}\{a_i\}\\ mx-mn=r-l \Leftrightarrow mx-mn-r+l=0 mx=i=lmaxr{ai},mn=i=lminr{ai}mxmn=rlmxmnr+l=0

考虑枚举右端点 r r r,然后维护每一个左端点 l l l对应的 m x − m n − r + l mx-mn-r+l mxmnr+l.

r r r变成 r + 1 r+1 r+1的时候,显然变化有三种:
1. r r r变成了 r + 1 r+1 r+1,也就是说要给左端点在 [ 1 , r − 1 ] [1,r-1] [1,r1]的区间的值都 − 1 -1 1.
2. m x mx mx会改变,且肯定是连续的一段区间 [ l ′ , r − 1 ] [l',r-1] [l,r1] m x mx mx改变.
3. m n mn mn会改变,同理是一段连续的区间 [ l ′ , r − 1 ] [l',r-1] [l,r1]改变.

以区间的左端点为下标建立线段树,就可以维护变化1了.

至于最值的变化,称一个极大的最值相同的子区间为一段,那么显然每次操作最多增加一个段,然后会将前面的一些段消除变成一段(与增加的段并成一段),容易发现这个时候被被操作的段的总数是线性的.

而显然,段的最大值必然是递减的,而每一段每次的变化量又是相同的(相当于可以直接区间加),所以可以用单调栈维护每一段,并用线段树维护变化量.

然后,答案就是每一次右端点变化时线段树中最小值的出现次数之和了(显然最小值必然为 0 0 0).

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn).

具体实现的时候,不需要真的在栈中存储每个节点代表的区间和最值,只需要存储一个区间右端点就可以通过一些操作得到其它信息了.

代码如下:

#include<bits/stdc++.h>
  using namespace std;

#define Abigail inline void
typedef long long LL;

const int N=300000,INF=(1<<30)-1;

int n,a[N+9];
struct tree{
  int min,add,cnt;
}tr[N*4+9];

void Pushup(int k){
  int ls=k<<1,rs=k<<1|1;
  tr[k].min=min(tr[ls].min,tr[rs].min);
  tr[k].cnt=(tr[ls].min==tr[k].min?tr[ls].cnt:0)+(tr[rs].min==tr[k].min?tr[rs].cnt:0); 
}

void Update_add(int k,int v){tr[k].min+=v;tr[k].add+=v;}

void Pushdown(int k){
  if (!tr[k].add) return;
  Update_add(k<<1,tr[k].add);Update_add(k<<1|1,tr[k].add);
  tr[k].add=0;
}

void Build(int L,int R,int k){
  if (L==R) {tr[k].cnt=1;return;}
  int mid=L+R>>1;
  Build(L,mid,k<<1);Build(mid+1,R,k<<1|1);
  Pushup(k);
}

void Change_add(int L,int R,int v,int l,int r,int k){
  if (L>R) return;
  if (L==l&&R==r) {Update_add(k,v);return;}
  Pushdown(k);
  int mid=l+r>>1;
  if (R<=mid) Change_add(L,R,v,l,mid,k<<1);
  else if (L>mid) Change_add(L,R,v,mid+1,r,k<<1|1);
    else Change_add(L,mid,v,l,mid,k<<1),Change_add(mid+1,R,v,mid+1,r,k<<1|1);
  Pushup(k);
}

int Query_cnt(int L,int R,int l,int r,int k){
  if (L>R) return 0;
  if (L==l&&R==r) return tr[k].min==0?tr[k].cnt:0;
  Pushdown(k);
  int mid=l+r>>1;
  if (R<=mid) return Query_cnt(L,R,l,mid,k<<1);
  else if (L>mid) return Query_cnt(L,R,mid+1,r,k<<1|1);
    else return Query_cnt(L,mid,l,mid,k<<1)+Query_cnt(mid+1,R,mid+1,r,k<<1|1);
}

int mx[N+9],mn[N+9],cmx,cmn;
LL ans;

Abigail into(){
  scanf("%d",&n);
  int x;
  for (int i=1;i<=n;++i){
    scanf("%d",&x);
    scanf("%d",&a[x]);
  }
}

Abigail work(){
  Build(1,n,1);
  for (int i=1;i<=n;++i){
  	Change_add(1,i-1,-1,1,n,1);
	for (;cmx&&a[mx[cmx]]<=a[i];--cmx)
	  Change_add(mx[cmx-1]+1,mx[cmx],a[i]-a[mx[cmx]],1,n,1);
	mx[++cmx]=i;
	for (;cmn&&a[mn[cmn]]>=a[i];--cmn)
	  Change_add(mn[cmn-1]+1,mn[cmn],a[mn[cmn]]-a[i],1,n,1);
	mn[++cmn]=i;
	ans+=(LL)Query_cnt(1,i,1,n,1);
  }
}

Abigail outo(){
  printf("%lld\n",ans);
}

int main(){
  into();
  work();
  outo();
  return 0;
}
单调栈是一种常用的数据结构,用于解决一类特定的问题,其中最常见的问题是找到数组中每个元素的下一个更大或更小的元素。在Codeforces编程竞赛中,单调栈经常被用于解决一些与数组相关的问题。 下面是单调栈的一般思路: 1. 创建一个空栈。 2. 从左到右遍历数组元素。 3. 对于每个元素,将其与栈顶元素进行比较。 - 如果当前元素小于等于栈顶元素,则将当前元素入栈。 - 如果当前元素大于栈顶元素,则将栈顶元素弹出,并将当前元素入栈。 4. 重复步骤3,直到遍历完所有元素。 这样,最后剩下的栈中元素就是没有下一个更大或更小元素的元素。在使用单调栈求解具体问题时,我们可以根据需要进行一些特定的操作。 例如,如果要找到一个数组中每个元素的下一个更大的元素,可以使用单调递减栈。具体操作如下: 1. 创建一个空栈和一个空结果数组。 2. 从左到右遍历数组元素。 3. 对于每个元素,将其与栈顶元素进行比较。 - 如果当前元素小于等于栈顶元素,则将当前元素入栈。 - 如果当前元素大于栈顶元素,则将栈顶元素弹出,并将其在结果数组中的位置记录为当前元素的下一个更大元素的索引。 4. 将当前元素入栈。 5. 重复步骤3和4,直到遍历完所有元素。 6. 结果数组中没有下一个更大元素的位置,可以设置为-1。 以下是一个使用单调递减栈求解下一个更大元素问题的示例代码: ```cpp #include <iostream> #include <stack> #include <vector> std::vector<int> nextGreaterElement(std::vector<int>& nums) { int n = nums.size(); std::vector<int> result(n, -1); std::stack<int> stack; for (int i = 0; i < n; i++) { while (!stack.empty() && nums[i] > nums[stack.top()]) { result[stack.top()] = i; stack.pop(); } stack.push(i); } return result; } int main() { std::vector<int> nums = {1,3, 2, 4, 5, 1}; std::vector<int> result = nextGreaterElement(nums); for (int i = 0; i < result.size(); i++) { std::cout << "Next greater element for " << nums[i] << ": "; if (result[i] != -1) { std::cout << nums[result[i]]; } else { std::cout << "None"; } std::cout << std::endl; } return 0; } ``` 以上代码将输出: ``` Next greater element for 1: 3 Next greater element for 3: 4 Next greater element for 2: 4 Next greater element for 4: 5 Next greater element for 5: None Next greater element for 1: None ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值