[广告位招租]
思路看不懂?代码来凑!(狗头保命)代码里也放了注释了
A - Minimizing Edges
题意
防AK,不读了 😦
B - No Time to Dry
题意
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 (vc≥va=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 s→i
- 最后,对于右区间 = 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
题意
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 t1−j
那么,假如行高 + 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 t1≥t2,那么满足条件数量的将受到第二行的影响,个数为 t 2 − j t_2-j t2−j
- 否则,即使第二行连续 1 1 1的数量多,但第一行还是得满足全为 1 1 1的条件,所以个数还是 t 1 − j t_1-j t