OI 笑传 #27

ABC 小思维。口胡为主。

ABC407E

反悔贪心题。

由这个题我们导出一个关于合法括号序列的充分必要条件:对于一个长度为 \(2N\) 的合法括号序列 \(S_{2N}\),对于其任意的一个前缀子串 \(S_{1,i},i\in [1,2N]\),这个子串中 ( 的数量一定大于等于 ) 的数量,且总的 () 数量均为 \(N\)

我们考虑依靠这个条件进行反悔贪心。具体是首先让所有字符都是 ),从左到右遍历每一个字符位置,如果不满足上面的条件就在前面找个 ) 把它变成 (,这样一定不会让前面的串不再满足上面的条件,于是我们找 ) 的最大值即可。

而且你会发现我们一直在保证合法的前提下减 ) 的数量,于是到最后这个串就是合法的。

ABC407F

涉及到单调栈算贡献这一块。

我们知道这种贡献是最值的可能出现最值相同的情况,此时如果直接无脑直接让这个最值贡献两边大于等于或是两边都大于的区间贡献会算错。

我们有一个简单的处理方法,就是让一个最值贡献的左半部分区间可以有和自己大小相同的数,右半部分不能有,这样贡献就对了。

剩余的就是分析贡献了,画画图即可。分三段。

这题还有区间加等差数列这一块,考虑静态那就是二次差分变成两次单点加,如果还要区间加也不急单独开一个一次差分完事了合并即可。

带查询的要上线段树,考虑给这个区间等差数列开个标记就是首项和公差,这两个也有结合律直接线段树即可。

写了个线段树维护等差数列加和区间加的代码,没有题,数是随便填的,拍了拍应该是对的:

code

Show me the code
#define rd read()
#define mkp make_pair
#define ls p<<1
#define rs p<<1|1
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#include<bits/stdc++.h>
using namespace std;
#define uex -73357733
typedef long long i64;
typedef unsigned long long u64;
typedef unsigned int u32;
typedef __int128 i128;
i64 read(){
  i64 x=0,f=1;
  char c=getchar();
  while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
  while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
  return x*f;
}
struct seg{
  int l;int r;
  i64 v;
  i64 lzt;
  i64 b;
  i64 d;
}t[10000];
i64 b1[100000];
void b(int p,int l,int r){
  t[p].l=l;t[p].r=r;
  t[p].v=t[p].lzt=t[p].b=t[p].d=0;
  if(l==r){
    t[p].v=b1[l];
    return ;
  }
  int mid=l+r>>1;
  b(ls,l,mid);b(rs,mid+1,r);
  t[p].v=t[ls].v+t[rs].v;
  return ;
}
void pushdown(int p){
  int k=t[p].lzt;
  int be=t[p].b;int de=t[p].d;
  t[p].lzt=t[p].b=t[p].d=0;
  int lenls=t[ls].r-t[ls].l+1;
  int lenrs=t[rs].r-t[rs].l+1;
  t[ls].v+=lenls*k+(be+ be+(lenls-1)*de)*lenls/2;
  t[rs].v+=lenrs*k+(be+lenls*de+ be+lenls*de+(lenrs-1)*de)*lenrs/2;
  t[ls].lzt+=k;t[rs].lzt+=k;
  t[ls].b+=be;t[ls].d+=de;
  t[rs].b+=be+lenls*de;t[rs].d+=de;
  return ;
}
void addc(int p,int l,int r,int k){
  if(l<=t[p].l&&t[p].r<=r){
    t[p].v+=(t[p].r-t[p].l+1)*k;
    t[p].lzt+=k;
    return ;
  }
  pushdown(p);
  int mid=t[p].l+t[p].r>>1;
  if(l<=mid)addc(ls,l,r,k);
  if(mid<r) addc(rs,l,r,k);
  t[p].v=t[ls].v+t[rs].v;
  return ;
}
void addl(int p,int l,int r,i64 b,i64 d){
  if(l<=t[p].l&&t[p].r<=r){
    t[p].v+=(b+(b+(t[p].r-t[p].l)*d))*(t[p].r-t[p].l+1)/2;
    t[p].b+=b;t[p].d+=d;
    return ;
  }
  pushdown(p);
  int mid=t[p].l+t[p].r>>1;
  if(l<=mid)addl(ls,l,r,b,d);
  if(mid<r) addl(rs,l,r,b+max((mid-max(l,t[p].l))+1,0)*d,d);
  t[p].v=t[ls].v+t[rs].v;
  return ;
}
i64 qu(int p,int l,int r){
  if(l<=t[p].l&&t[p].r<=r)return t[p].v;
  pushdown(p);
  int mid=t[p].l+t[p].r>>1;
  i64 res=0;
  if(l<=mid)res+=qu(ls,l,r);
  if(mid<r) res+=qu(rs,l,r);
  return res;
}
int main(){
  
  int n,q;cin>>n>>q;
  for(int i=1;i<=n;i++)cin>>b1[i];
  b(1,1,n);
  for(int i=1;i<=q;i++){
    int op;cin>>op;
    if(op==1){
      int l,r,c;cin>>l>>r>>c;
      addc(1,l,r,c);
      for(int j=l;j<=r;j++)b1[j]+=c;
    }
    if(op==2){
      int l,r,b,d;cin>>l>>r>>b>>d;
      addl(1,l,r,b,d);
      for(int j=l,a=b;j<=r;j++,a+=d)b1[j]+=a;
    }
    if(op==3){
      for(int j=1;j<=n;j++){
        cout<<qu(1,j,j)<<' ';
      }
      cout<<'\n';
      cout<<"- implementations: "<<'\n';
      for(int j=1;j<=n;j++){
        cout<<b1[j]<<' ';
      }
      cout<<'\n';
      cout<<'\n';
    }
  }
  
  return 0;
}

ABC410E

经典技巧把答案扔状态里优化转移,直接做即可。

ABC410F

很有意思的题。

考虑把 .\(1\) #\(-1\),然后就变成了找子矩形和为 \(0\)

然后到这一步我就不会转化了。。。

想想一维该咋做。我们当然可以做个前缀和,从左到右扫一扫把前缀和值放个桶里面,由于前缀和的性质,前面前缀和一样是这个的位置代表这两个位置之间的子段和为 \(0\),统计一下是 \(O(n)\) 能做的。

二维呢?我们考虑钦定子矩形的高,这样把每一列的和放到一维数组里,然后前缀和,然后就是上面的做法。

于是我们考虑 \(O(n^2)\) 枚举高,也就是用两个指针卡住高的区间,再 \(O(m)\) 更新一维数组,再 \(O(m)\) 做一遍,时间复杂度是 \(O(n^2m)\)

但是枚举高,高可能很高,于是我们从宽高里面挑小的那一个枚举。

这就类似一个根号分治了,时间复杂度是 \(O(\max(n,m)\times \min(n,m)^2)\),最坏也就是 \(500^3\) 量级,能过。

posted @ 2025-11-14 17:12  hm2ns  阅读(16)  评论(0)    收藏  举报