2022牛客寒假算法基础集训营4 B、 G两题

博客探讨了如何使用线段树解决区间最大值查询并维护区间进制,以及计算数组子序列权值的方法。通过建立两棵线段树分别维护区间最大值和不同进制的信息,并详细解释了如何进行修改和查询操作。此外,还介绍了计算子序列权值的优化策略,通过对数组排序并计算每个元素对答案的贡献来实现。

B

  • 这是一道很好的线段树练习题,思路很清晰,查询区间进制最小值所对应的进制显然应该是区间最大值+1,所以使用一个线段树维护区间最大值;那么如何维护区间进制呢?让另外一颗线段树记录数字信息即可
  • 之前没做过区间进制的问题,记录一下
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int N = 1e5 + 100;
const int MOD = 1e9 + 7;
string s;
struct Tree_Max{
  int l;
  int r;
  int MAX;
}tr_max[N << 2];
void Push_UP_Max(int u){
  tr_max[u].MAX = max(tr_max[u << 1].MAX, tr_max[u << 1 | 1].MAX);
}
void Build_Max_Tree(int u, int l, int r){
  if(l == r){
    tr_max[u] = {l, r, s[l] - '0'};
  }else{
    tr_max[u] = {l, r};
    int mid = (r - l >> 1) + l;
    Build_Max_Tree(u << 1, l, mid);
    Build_Max_Tree(u << 1 | 1, mid + 1, r);
    Push_UP_Max(u);
  }
}
void modify_Max(int u, int x, int y){
  if(tr_max[u].l == tr_max[u].r){
    tr_max[u].MAX = y;
  }else{
    int mid = (tr_max[u].l + tr_max[u].r >> 1);
    if(x <= mid){
      modify_Max(u << 1, x, y);
    }else{
      modify_Max(u << 1 | 1, x, y);
    }
    Push_UP_Max(u);
  }
}
int query_max(int u, int l, int r){
  if(tr_max[u].l >= l && tr_max[u].r <= r){
    return tr_max[u].MAX;
  }else{
    int mid = (tr_max[u].l + tr_max[u].r >> 1);
    int ans = -1;
    if(l <= mid){
      ans = max(ans, query_max(u << 1, l, r));
    }
    if(mid < r){
      ans = max(ans, query_max(u << 1 | 1, l, r));
    }
    return ans;
  }
}
int fastpow(ll base, int power){//注意爆ll,以后快速幂就统一long long
  ll ans = 1;
  while(power > 0){
    if(power & 1) ans = ans * base % MOD;
    base = base * base % MOD;
    power >>= 1;
  }
  return (int)ans;
}
struct Tree_Base{
  int l;
  int r;
  int val;
}tr_Base[N << 2][11];// 2 - 10进制
void Push_Up_Base(int u, int k){
  int sz = tr_Base[u << 1 | 1][k].r - tr_Base[u << 1 | 1][k].l + 1;//注意这里是右子树
  tr_Base[u][k].val = (1ll * tr_Base[u << 1][k].val * fastpow(k, sz) % MOD + tr_Base[u << 1 | 1][k].val) % MOD;
}
void Build_Base_Tree(int u, int l, int r, int k){//建立k进制的线段树
  if(l == r){
    tr_Base[u][k] = {l, r, s[l] - '0'};
  }else{
    tr_Base[u][k] = {l, r};
    int mid = (r - l >> 1) + l;
    Build_Base_Tree(u << 1, l, mid, k);
    Build_Base_Tree(u << 1 | 1, mid + 1, r, k);
    Push_Up_Base(u, k);
  }
}
void modify_Base(int u, int x, int y, int k){
  if(tr_Base[u][k].l == tr_Base[u][k].r){
    tr_Base[u][k].val = y;
  }else{
    int mid = (tr_Base[u][k].l + tr_Base[u][k].r >> 1);
    if(x <= mid) modify_Base(u << 1, x, y, k);
    else modify_Base(u << 1 | 1, x, y, k);
    Push_Up_Base(u, k);
  }
}
int query_Base(int u, int l, int r, int k){
  if(tr_Base[u][k].l >= l && tr_Base[u][k].r <= r){
    return tr_Base[u][k].val;
  }else{
    int mid = tr_Base[u][k].l + tr_Base[u][k].r >> 1;
    ll ans = 0;
    if(l <= mid){
      ans += query_Base(u << 1, l, r, k);
    }
    ans *= fastpow(k, min(r, tr_Base[u][k].r) - mid);// !!!注意这里范围
    ans %= MOD;
    if(mid < r){
      ans += query_Base(u << 1 | 1, l, r, k);
    }
    ans %= MOD;
    return (int)ans;
  }
}
int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n, q;
  cin >> n >> q;
  s.resize(n + 1);
  for(int i=1;i<=n;i++) cin >> s[i];
  Build_Max_Tree(1, 1, n);
  for(int i=2;i<=10;i++){
    Build_Base_Tree(1, 1, n, i);
  }
  while(q--){
    int opt, x, y;
    cin >> opt >> x >> y;
    if(opt == 1){
      modify_Max(1, x, y);
      for(int i=2;i<=10;i++){
        modify_Base(1, x, y, i);
      }
    }else{
      cout << query_Base(1, x, y, max(2, query_max(1, x, y) + 1)) << '\n';
    }
  }
  return 0;
}

G

定义子序列权值为子序列最小值和最大值的乘积,问一个数组中所有非空子序列的权值乘积是多少,取模

  • 理解题解中所讲的一个长度为kkk的区间,中间的数对答案的贡献是2k−22^{k-2}2k2,但是此题有更好的办法
  • 考虑枚举每一个数,当然首先把它们从小到大排序,设这个数是第iii个,iii从0开始,那么因为已经排好序了,那么它能够对答案造成多少贡献呢?因为贡献一定是这个数是子序列里面的最大值或者是最小值,如果成立为最小值,那么子序列的其他数一定是在这个数后面的某些数或者没有,它后面一共有2n−i−12^{n-i-1}2ni1个数,那么根据一个长度为nnn的数组的子序列个数一共是2n2^{n}2n(包括空序列),所以这个时候这个数对答案的贡献是2n−i−12^{n-i-1}2ni1;同理可得当这个数成立为最大值的时候,对答案的贡献是2i2^{i}2i,所以答案应该是∑i=0n−1a[i]2i+2n−i−1\sum_{i=0}^{n-1} a[i]^{2^{i}+2^{n-i-1}}i=0n1a[i]2i+2ni1,当然首先要欧拉降幂降指数,因为模数是一个质数,所以欧拉函数值就是MOD−1MOD-1MOD1
  • 看jiangly代码突然悟到了
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int MOD = 1e9 + 7;
ll fastpow(ll base, ll power, ll MOD){
    ll ans = 1;
    while(power){
        if(power & 1) ans = ans * base % MOD;
        base = base * base % MOD;
        power >>= 1;
    }
    return ans;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n;
    cin >> n;
    vector<ll> POW(n);
    POW[0] = 1ll;
    for(int i=1;i<n;i++){
        POW[i] = POW[i - 1] * 2 % (MOD - 1);
    }
    ll ans = 1;
    vector<int> a(n);
    for(auto &i : a) cin >> i;
    sort(a.begin(), a.end());
    for(int i=0;i<n;i++){
        ans *= fastpow(a[i], POW[i] + POW[n - i - 1], MOD);
        ans %= MOD;
    }
    cout << ans;
    return 0;
}
### 关于2020年寒假算法基础集训营中的欧几里得算法 在2020年的寒假算法基础集训营中,确实存在涉及欧几里得算法的相关题目。具体来说,在第四场竞赛的第一题即为“A. 欧几里得”,该题目的核心在于利用扩展欧几里得定理来解决问题[^5]。 #### 扩展欧几里得算法简介 扩展欧几里得算法主要用于求解形如 ax + by = gcd(a, b) 的线性不定方程的一组特解(x,y),其中gcd表示最大公约数。此方法不仅能够计算两个整数的最大公因数,还能找到满足上述条件的具体系数x和y。 对于给定的数据范围较小的情况可以直接通过递归来实现;而对于较大数据则需考虑效率优化问题。下面给出了一段基于C++语言编写的用于解决此类问题的模板代码: ```cpp #include<bits/stdc++.h> #define int long long using namespace std; // 定义全局变量存储结果 int x, y; void ex_gcd(int a, int b){ if(b == 0){ x = 1; y = 0; return ; } ex_gcd(b, a % b); int tmp = x; x = y; y = tmp - (a / b) * y; } ``` 这段程序实现了经典的扩展欧几里得算法逻辑,并且可以作为处理类似问题的基础工具函数调用。 #### 实际应用案例分析 回到原题本身,“A. 欧几里得”的解答思路就是先预处理斐波那契数列前若干项数存入数组`a[]`内以便快速查询,之后针对每一次询问直接输出对应位置处两相邻元素之和即可得出最终答案。这实际上巧妙运用到了广为人知的裴蜀定理——任意一对互质正整数都可由它们自身的倍数组合而成,而这里正是借助了这一性质简化了解决方案的设计过程。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Clarence Liu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值