分块入门练习-3

练习-1及分块介绍  练习-2


7.区间乘法及区间加法,单点求和

给出一个长为 N 的数列,以及 N 个操作,操作涉及区间乘法,区间加法,单点询问。

数据范围:1 \leq n \leq 100000, ans,op\_val \in int

 

线段树的经典模板题(雾 (*•̀ㅂ•́)و(不过我改成区间求和过去TLE了

思想还是维护块的乘积mtag[],加的值atag[]

  • 单点查询时ans[i]=val[i]*mtag[blg[i]]+atag[blg[i]]
  • 对于区间修改涉及一个reset()操作,即涉及不完整块的操作,首先重置对应整块的val[i](因为加法乘法顺序的原因必须reset()后再进行修改)
  • 区间加add():不完整的块reset()后更新val[i],整块更新atag[]
  • 区间乘multi():不完整块reset()后更新val[i],整块更新mtag[],atag[]两者
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
using namespace std;

using LL = long long;
const int MOD = 1e4 + 7;
int N;
LL block, val[100005], blg[100005];
LL atag[1005], mtag[1005], sum[1005];
void add(int l, int r, int key);
void multi(int l, int r, int key);

int main(){
  ios::sync_with_stdio(false);
  int i;
  cin >> N, block = sqrt(N);
  for(i = 1; i <= N; i++) cin >> val[i];
  for(i = 1; i <= N; i++)
    blg[i] = (i - 1) / block + 1;
  for(i = 1; i <= blg[N]; i++) mtag[i] = 1, atag[i] = 0;
  for(i = 1; i <= N; i++){
    int op, x, y, key;
    cin >> op >> x >> y >> key;
    if(op == 0) add(x, y, key);
    else if(op == 1) multi(x, y, key);
    else if(op == 2) cout << (val[y] * mtag[blg[y]] + atag[blg[y]]) % MOD << endl;
  }
  return 0;
}

void reset(int block_id);
void add(int l, int r, int key){
  int i, top;
  reset(blg[l]);
  for(i = l, top = min(blg[l] * block, 1LL * r); i <= top; i++){
    val[i] += key, val[i] %= MOD;
    sum[blg[i]] += key, sum[blg[i]] %= MOD;
  }

  if(blg[l] != blg[r]){
    reset(blg[r]);
    for(i = (blg[r] - 1) * block + 1; i <= r; i++){
      val[i] += key, val[i] %= MOD;
      sum[blg[i]] += key, sum[blg[i]] %= MOD;
    }
  }

  for(i = blg[l] + 1; i <= blg[r] - 1; i++){
    sum[i] += block * key, sum[i] %= MOD;
    atag[i] += key, atag[i] %= MOD;
  }
}

void multi(int l, int r, int key){
  int i, top;
  reset(blg[l]);
  for(i = l, top = min(blg[l] * block, 1LL * r); i <= top; i++){
    sum[blg[i]] = (sum[blg[i]] - val[i] + MOD) % MOD;
    val[i] *= key, val[i] %= MOD;
    sum[blg[i]] += val[i], sum[blg[i]] %= MOD;
  }

  if(blg[l] != blg[r]){
    reset(blg[r]);
    for(i = (blg[r] - 1) * block + 1; i <= r; i++){
      sum[blg[i]] = (sum[blg[i]] - val[i] + MOD) % MOD;
      val[i] *= key, val[i] %= MOD;
      sum[blg[i]] += val[i], sum[blg[i]] %= MOD;
    }
  }

  for(i = blg[l] + 1; i <= blg[r] - 1; i++){
    sum[i] *= key, sum[i] %= MOD;
    atag[i] *= key, atag[i] %= MOD;
    mtag[i] *= key, mtag[i] %= MOD;
  }
}

void reset(int x){
  int i, top;
  for(i = (x - 1) * block + 1, top = min(x * block, 1LL * N); i <= top; i++)
    val[i] = (val[i] * mtag[blg[i]] + atag[blg[i]]) % MOD;
  mtag[x] = 1, atag[x] = 0;
}

8. 区间染色,区间颜色种类查询

给出一个长为 N 的数列,以及 N 个操作,操作涉及区间询问等于一个数 c的元素,并将这个区间的所有元素改为 c

数据范围:1 \leq n \leq 100000, ans,op\_val \in int

 

很诡异的操作,但仔细分析和第五题有点像.

可以发现虽然一开始一个块颜色很多,但经过多次染色后很多区间都是相同的颜色

操作即变为:

  • 不完整块,先统计再标记对应块颜色不唯一
  • 完整块,颜色唯一则直接判断,否则直接遍历(若数据十分不随机可以考虑记忆化)

若数据较为随机,每次操作摊还也是O(\sqrt N)

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;

using LL = long long;
LL N;
LL block, val[100005], blg[100005];
LL color[1005];
int update(int, int, int);

int main(){
  ios::sync_with_stdio(false);
  int i;
  cin >> N, block = sqrt(N);
  for(i = 1; i <= N; i++) cin >> val[i];
  for(i = 1; i <= N; i++) blg[i] = (i - 1) / block + 1;
  for(i = 1; i <= blg[N]; i++) color[i] = -1e11;
  for(i = 1; i <= N; i++){
    int x, y, key;
    cin >> x >> y >> key;
    cout << update(x, y, key) << endl;
  }
  return 0;
}

void reset(int);
int update(int l, int r, int key){
  int i, j, top, res = 0;
  top = min(blg[l] * block, 1LL * r);
  if(color[blg[l]] != -1e11)
    res += (top - l + 1) * (color[blg[l]] == key);
  else{
    for(i = l; i <= top; i++)
      if(val[i] == key) res++;
  }
  reset(blg[l]);
  color[blg[l]] = -1e11;
  for(i = l; i <= top; i++) val[i] = key;

  if(blg[l] != blg[r]){
    if(color[blg[r]] != -1e11)
      res += (r - (blg[r] - 1) * block) * (color[blg[r]] == key);
    else{
      for(i = (blg[r] - 1) * block + 1; i <= r; i++)
        if(val[i] == key) res++;
    }
    reset(blg[r]);
    color[blg[r]] = -1e11;
    for(i = (blg[r] - 1) * block + 1; i <= r; i++) val[i] = key;
  }

  for(i = blg[l] + 1; i <= blg[r] - 1; i++){
    if(color[i] == -1e11){
      for(j = (i - 1) * block + 1; j <= block * i; j++)
        if(val[j] == key) res++;
    }
    else
      res += block * (color[i] == key);
    color[i] = key;
  }
  return res;
}

void reset(int x){
  if(color[x] == -1e11) return;
  int i, top;
  for(i = (x - 1) * block + 1, top = min(x * block, 1LL * N); i <= top; i++)
    val[i] = color[blg[i]];
}

9.区间无修改众数查询(在线算法)

给出一个长为N的数列,以及N 个操作,操作涉及询问区间的最小众数。

数据范围:1 \leq n \leq 100000, ans,op\_val \in int

 

首先将每个数离散化,再开辟一个ve[]记录出现的位置(这样就可以利用\_bound操作得到一个数在某区间的出现次数)

  • 对于整块,首先预处理dp可以得到dp[i][j]i,i+1,..,j个整块的众数(这里是O(\frac{N^2}{block}))暴力枚举
  • 查询时,众数在不完整的块中或者为整块整体的众数(dp[i][j]直接得到).再进行枚举判断即可O(block*logN)

可以得到分块大小取\frac{\sqrt N}{logN}较优

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
#include <set>
#include <map>
using namespace std;

int N;
int block, val[100005], blg[100005];
int mode[6005][6005];
int hashx[100005], cnt[100005];
map<int, int> mp;
vector<int> ve[100005];
void pre_mode(int block_id);
int query(int, int);

int main(){
  ios::sync_with_stdio(false);
  int i, ind;
  cin >> N, block = sqrt(N) / log(N);
  for(i = 1, ind = 0; i <= N; i++){
    cin >> val[i];
    if(!mp[val[i]]){
      mp[val[i]] = ++ind;
      hashx[ind] = val[i];
    }
    val[i] = mp[val[i]];
    ve[val[i]].push_back(i);
  }
  for(i = 1; i <= N; i++) blg[i] = (i - 1) / block + 1;
  for(i = 1; i <= blg[N]; i++) pre_mode(i);
  for(i = 1; i <= N; i++){
    int x, y;
    cin >> x >> y;
    cout << hashx[query(x, y)] << endl;
  }
  return 0;
}

int gettimes(int, int, int);
int query(int l, int r){
  int res, mx;
  res = mode[blg[l] + 1][blg[r] - 1];
  mx = gettimes(l, r, res);
  int i, top;
  for(i = l, top = min(blg[l] * block, r); i <= top; i++){
    int tmp = gettimes(l, r, val[i]);
    if(tmp > mx || (tmp == mx && hashx[val[i]] < hashx[res]))
      res = val[i], mx = tmp;
  }
  if(blg[l] != blg[r])
    for(i = (blg[r] - 1) * block + 1; i <= r; i++){
      int tmp = gettimes(l, r, val[i]);
      if(tmp > mx || (tmp == mx && hashx[val[i]] < hashx[res]))
        res = val[i], mx = tmp;
    }
  return res;
}

int gettimes(int l, int r, int x){
  return upper_bound(ve[x].begin(), ve[x].end(), r) - lower_bound(ve[x].begin(), ve[x].end(), l);
}

void pre_mode(int x){
  memset(cnt, 0, sizeof(cnt));
  int i, j, mx = 0, res = 0;
  for(i = (x - 1) * block + 1; i <= N; i++){
    cnt[val[i]]++;
    j = blg[i];
    if(cnt[val[i]] > mx || (cnt[val[i]] == mx && hashx[val[i]] < hashx[res]))
      mx = cnt[val[i]], res = val[i];
    mode[x][j] = res;
  }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值