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

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

  • 理解题解中所讲的一个长度为 k k k的区间,中间的数对答案的贡献是 2 k − 2 2^{k-2} 2k2,但是此题有更好的办法
  • 考虑枚举每一个数,当然首先把它们从小到大排序,设这个数是第 i i i个, i i i从0开始,那么因为已经排好序了,那么它能够对答案造成多少贡献呢?因为贡献一定是这个数是子序列里面的最大值或者是最小值,如果成立为最小值,那么子序列的其他数一定是在这个数后面的某些数或者没有,它后面一共有 2 n − i − 1 2^{n-i-1} 2ni1个数,那么根据一个长度为 n n n的数组的子序列个数一共是 2 n 2^{n} 2n(包括空序列),所以这个时候这个数对答案的贡献是 2 n − i − 1 2^{n-i-1} 2ni1;同理可得当这个数成立为最大值的时候,对答案的贡献是 2 i 2^{i} 2i,所以答案应该是 ∑ i = 0 n − 1 a [ i ] 2 i + 2 n − i − 1 \sum_{i=0}^{n-1} a[i]^{2^{i}+2^{n-i-1}} i=0n1a[i]2i+2ni1,当然首先要欧拉降幂降指数,因为模数是一个质数,所以欧拉函数值就是 M O D − 1 MOD-1 MOD1
  • 看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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Clarence Liu

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

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

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

打赏作者

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

抵扣说明:

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

余额充值