ZJNU 2021-07-16 个人排位赛5 部分题解

[广告位招租]

思路看不懂?代码来凑!(狗头保命)代码里也放了注释了

Another One


A - Minimizing Edges

Link

题意

防AK,不读了 😦




B - No Time to Dry

Link

题意

Bessie想涂一面墙,初始时墙是无色的

Bessie每次涂色只能用暗色(编号大的)覆盖亮色(编号小的)

Q Q Q次询问,每次询问给定一个区间 [ l , r ] [l,r] [l,r]

如果只对 [ l , r ] [l,r] [l,r]区间进行涂色,其余区间不涂色,那么最少的涂色次数为多少?

标签

Monotonous Stack

Binary Index Tree

思路

第二场个人赛[H - No Time to Paint]的另外一个版本,这次涂的是区间内部

由于区间左右侧是不涂色的,所以只能单独处理每个询问表示的小区间

在线做法(适用于只有一个询问)是单调栈的基础题,但本题的询问量用单调栈模拟肯定会超时

但我们想到,单调栈做法的原理实际上就是:

  • 如果两个值相同的点 a , b   ( v a = v b ,   a < b ) a,b\ (v_a=v_b,\ a\lt b) a,b (va=vb, a<b)之间都是大于它们的值的点 c   ( v c ≥ v a = v b ,   a < c < b ) c\ (v_c\ge v_a=v_b,\ a\lt c\lt b) c (vcva=vb, a<c<b),那么如果这两个点都被用到,就可以只涂一次使得两个点都涂上正确的颜色

所以考虑离线做法,处理顺序是从左到右,所以先把所有询问存起来,按右区间从小到大排序

假如我们处理到了位置 p p p,则只相应右区间为 p p p的询问即可

按顺序处理,像只做一次询问那样维护一个单调栈:

  • 假设当前处理到了位置 i i i,表示的值是 a r i ar_i ari
  • 如果栈顶表示的值比 a r i ar_i ari大,那么就将其弹出栈,直到栈顶的值小于等于 a r i ar_i ari(维护单调性)
  • 如果栈顶的值 a r s ar_s ars等于 a r i ar_i ari,说明如果位置 s s s被包含在右区间 ≥ i \ge i i的询问中,那么 s s s i i i可以仅通过一次涂色得到(中间的值根据单调栈处理,肯定都是大于等于它们的)
  • 所以将 s s s的位置标记一个 1 1 1,表示如果用到这个位置就减去一次涂色次数
  • 更新栈顶位置 s → i s\rightarrow i si
  • 最后,对于右区间 = i =i =i的询问,答案就是区间长度减去区间中被标记的次数

被标记的次数可以用树状数组/线段树来求出

代码

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;

const int MAXN=200010;
struct BIT
{
   
    int a[MAXN];
    void update(int p,int v){
   for(;p<MAXN;p+=p&-p)a[p]+=v;}
    int query(int p){
   int r=0;for(;p;p-=p&-p)r+=a[p];return r;}
    int query(int l,int r){
   return query(r)-query(l-1);}
}tree;

struct query
{
   
    int l,r,len,id;
    bool operator < (const query& a) const
    {
   
        // 主要按照右区间从小到大排序
        if(r!=a.r)
            return r<a.r;
        return l<a.l;
    }
}qr[MAXN];
int ar[MAXN];
int ans[MAXN];

void solve()
{
   
    int n,q;
    cin>>n>>q;
    rep(i,1,n)
        cin>>ar[i];
    rep(i,1,q)
    {
   
        cin>>qr[i].l>>qr[i].r;
        // 记录区间长度及原询问位置
        qr[i].len=qr[i].r-qr[i].l+1;
        qr[i].id=i;
    }
    sort(qr+1,qr+q+1);
    
    int pos=1;
    stack<int> sk;
    rep(i,1,n)
    {
   
        // 单调栈,将大于当前值的弹出栈
        while(!sk.empty()&&ar[sk.top()]>ar[i])
            sk.pop();
        // 如果栈顶的值与当前值相同
        // 说明如果栈顶的值表示的位置被引用到
        // 可以直接涂色涂到当前位置
        // 所以在栈顶位置+1,更新栈顶
        if(!sk.empty()&&ar[sk.top()]==ar[i])
        {
   
            tree.update(sk.top(),1);
            sk.pop();
        }
        sk.push(i);
        // 处理右区间与当前位置相同的询问
        while(pos<=q&&qr[pos].r==i)
        {
   
            ans[qr[pos].id]=qr[pos].len-tree.query(qr[pos].l,qr[pos].r);
            pos++;
        }
    }
    
    rep(i,1,q)
        cout<<ans[i]<<'\n';
}
int main()
{
   
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    solve();
    return 0;
}



C - Just Green Enough

Link

题意

N × N N\times N N×N的网格,每个格子都有一个 1 1 1 200 200 200之间的值

定义一个子矩阵是”足够绿但不是特别绿“的,当且仅当子矩阵中的最小值严格为 100 100 100

问存在多少个这样的子矩阵

标签

State Compression

Implementation & Preprocessing

Counting

思路

要求找出最小值严格 = 100 =100 =100的子矩阵数量

实际上可以找出最小值 ≥ 100 \ge 100 100的数量,再找出最小值 > 100 \gt 100 >100的数量

两者相减,便能得到答案


如果我们要找最小值 ≥ x \ge x x的数量,实际上可以压缩一下状态方便处理,将 ≥ x \ge x x的值标记为 1 1 1,否则标记为 0 0 0

然后需要处理的就是全为 1 1 1的子矩阵数量,显然 O ( n 4 ) O(n^4) O(n4)枚举左上角与右下角的点是行不通的,所以考虑降一级复杂度

先还是 O ( n 2 ) O(n^2) O(n2)枚举子矩阵的左上角 ( i , j ) (i,j) (i,j)

如果我们想知道行高为 1 1 1的子矩阵有多少个:

  • 那也就是从 ( i , j ) (i,j) (i,j)向右侧寻找,要保证子矩阵内的值全为 1 1 1,所以一旦遇到 0 0 0就结束寻找
  • 假设 ( i , j ) (i,j) (i,j)右侧第一个 0 0 0的坐标为 ( i , t 1 ) (i,t_1) (i,t1),那么满足条件的子矩阵个数就是 t 1 − j t_1-j t1j

那么,假如行高 + 1 +1 +1,如果想知道有多少个行高为 2 2 2的子矩阵:

  • 这需要在保证第一行全为 1 1 1的基础上,在第二行中去找
  • 假设 ( i + 1 , j ) (i+1,j) (i+1,j)右侧第一个 0 0 0的坐标是 ( i + 1 , t 2 ) (i+1,t_2) (i+1,t2)
  • 如果 t 1 ≥ t 2 t_1\ge t_2 t1t2,那么满足条件数量的将受到第二行的影响,个数为 t 2 − j t_2-j t2j
  • 否则,即使第二行连续 1 1 1的数量多,但第一行还是得满足全为 1 1 1的条件,所以个数还是 t 1 − j t_1-j t
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值