test20230720(提高组400++试题第二组 模拟考试)

发布时间 2023-07-20 22:34:00作者: Daniel_yzy

前言

考得很烂

image

szy 好强!!!

赛时得分明细:

hugclose wayhome jewel Tolal rank
6 10 0 16 20

赛后改题明细:

hugclose wayhome jewel
100 100 100

曾经的王牌累了

hugclose

题面

给定 \(n\) 个数,\(Q\) 个询问。每一次询问给出一个 \(x\),问 \(\max\limits_{i=l}^r a_i \oplus x\)

题解

他们都说是可持久化01-trie板子,不过我没学过。看来,得先自己学一遍。

其实它和可持久化线段树2的思路差不多。对于 \(n\) 个点,每一个点都对其做一个前缀可持久化01-trie。

然后再将 \(l-1\)\(r\) 两棵树的信息合并。不过信息合并的和可持久化线段树的信息合并不同,因为可持久化01-trie的前后形态不同。所以我们要记录一个 \(siz_i\) 表示以第 \(i\) 个节点为根的子树节点个数。

由于需要异或值最大。每次贪心的从根节点走,如果 \(x\) 的第 \(i\) 位为 \(1\),那么就走 \(0\),否则就走 \(1\)。因为每个数的二进制长度不相同,所以建出来树可能会“参差不齐”,为了避免这种情况。我们应把每一个数的二进制前补零,使得每一个数形成的链长度相同。

一个点是否能跳取决于其子树大小是否比 \(l-1\) 棵树上相同位置上的子树要大,如果大,则说明有新数加入进来了,反之则没有。

然后就是一些细节问题:

Q:\(l-1\)\(r\) 树上的两个指针再同时跳的时候,由于树的形态不相同。有可能会出现一个指针跳出去了,一个指针还在树上的情况。这个要怎么处理。

A:其实不用管跳出去的指针,因为指针记录的空节点的子树大小为零。

还得多多学算法,看眼界。

代码

#include <bits/stdc++.h>
#define int long long
#define H 19260817
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define MOD 1000003
#define mod 1000000007

using namespace std;

inline int read() {
  rint x=0,f=1;char ch=getchar();
  while(ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
  while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
  return x*f;
}

void print(int x){
  if(x<0){putchar('-');x=-x;}
  if(x>9){print(x/10);putchar(x%10+'0');}
  else putchar(x+'0');
  return;
}

const int N = 2e5 + 10;

int n, q, t[N<<5][2], root[N<<5], siz[N<<5], idx, a[N];

void insert(int x, int y, int val) {
  siz[y] = siz[x] + 1;
  for (int i = 31; i >= 0; --i) {
    int o = (val >> i) & 1;
    t[y][o^1] = t[x][o^1];
    if(!t[y][o]) t[y][o] = ++idx;
    y = t[y][o];
    x = t[x][o];
    siz[y] = siz[x] + 1;
  }
}

int query(int x, int y, int val) {
  int ans = 0;
  for (int i = 31; i >= 0; i--) {
    int o = (val >> i) & 1;
    if(siz[t[x][o ^ 1]] < siz[t[y][o ^ 1]]) {
      ans += (1 << i);
      x = t[x][o ^ 1];
      y = t[y][o ^ 1];
    } else {
      x = t[x][o];
      y = t[y][o];
    }
  } 
  return ans;
}

signed main() {
//  freopen("hugclose.in", "r", stdin);
//  freopen("hugclose.out", "w", stdout);
  n = read(), q = read();
  For(i,1,n) {
    a[i] = read();
    root[i] = ++idx;
    insert(root[i - 1], root[i], a[i]);
  }
  while(q--) {
    int x = read(), l = read(), r = read(); 
    l++, r++;
    cout << query(root[l-1], root[r], x) << '\n';
  }
  return 0;
}

wayhome

题面

Jim 走夜路很怕黑,时时刻刻需要手电筒。有 \((N+1)\) 个充电站,\(0\) 是起点 \(N\) 是终点。第 \(i\) 个充电站到下一个充电站的距离为 \(D_i\),单位充电量花费为 \(P_i\)。每移动一个单位长度电量消耗一个单位电。如果 Jim 无法保证全程
手电筒都亮着输出 \(-1\)。否则求整个旅途的最少花费。

题解

贪心。

对于走到的每一个充电桩,贪心的选择此时充或不充。每一次判断剩余电量到下一个比此充电桩 \(P_i\) 更小的充电桩是否足够。若不足够,则把电量充满,到下一个充电桩做同样的贪心决策;反之,则将电量充到到下一个比此充电桩 \(P_i\) 更小的充电桩所需的电量。这样可以保证到了那一个点电量可以用完,并且充到廉价的电。、

找下一个比此充电桩 \(P_i\) 更小的充电桩可以用单调栈。然后模拟即可。

代码

#include <bits/stdc++.h>
#define int long long
#define H 19260817
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define MOD 1000003
#define mod 1000000007

using namespace std;

inline int read() {
  rint x=0,f=1;char ch=getchar();
  while(ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
  while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
  return x*f;
}

void print(int x){
  if(x<0){putchar('-');x=-x;}
  if(x>9){print(x/10);putchar(x%10+'0');}
  else putchar(x+'0');
  return;
}

const int N = 5e5 + 10;

int n, d[N], p[N], T, sum[N], stk[N], top, nx[N], ans, ram;

signed main() {
  n = read(), T = read();
  For(i,1,n) {
    d[i] = read(), p[i] = read();
    sum[i] = sum[i - 1] + d[i];
  }
  For(i,1,n) {
    while(top && p[stk[top]] > p[i]) {
      nx[stk[top]] = i;
      top--;
    } 
    stk[++top] = i;
  }
  while(top) {
    nx[stk[top--]] = n+1;
  }
  ram = 0;
  for (int i = 1; i <= n; i++) {
    ram -= d[i-1];
    if(ram < 0) {
      puts("-1");
      return 0;
    }
    int dis = min(sum[nx[i]-1] - sum[i-1], T);
    if(dis > ram) ans += (dis - ram) * p[i], ram = dis;
  }
  cout << ans << '\n';
  return 0;
}

jewel

题面

给定 \(n\) 个数,\(m\) 次询问。每次询问给出 \(l, r\),求 \(\min\limits_{l\le x<y\le n}^{a_x = a_y} |x-y|\)

题解

考场上想出来了,但没做出来(原因是线段树没有 pushup)。

\(d_i\) 表示从左往右第一个与 \(i\) 位置上的数相等的距离。下意识就是转化为区间最小值。线段树维护即可。

不过你会发现 \(d_i\) 的答案贡献和区间有关。但只和区间右端点有关。于是将询问离线,按右端点排序。再维护 \(d_i\) 的区间最小值,单点修改,线段树能胜任。右端点没变,直接查区间最小。右端点变动,将变动的部分区间的贡献删去,再查区间最小。

“将变动的部分区间的贡献删去”这一操作还需维护一个 \(t_i\) 表示从 \(i\) 这个数往左看第一个等于 \(i\) 这个位置上的数的位置。然后当右端点变动的时候,将变动的部分区间所对应的 \(t_i\) 暴力修改即可。(每个点最多只会被修改一次,所以时间复杂度依然为 \(O((q+n)\log n)\))。

代码

#include <bits/stdc++.h>
#define int long long
#define H 19260817
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define MOD 1000003
#define mod 1000000007
#define inf 0x3f3f3f3f3f3f3f3f
#define ls p<<1
#define rs p<<1|1

using namespace std;

inline int read() {
  rint x=0,f=1;char ch=getchar();
  while(ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
  while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
  return x*f;
}

void print(int x){
  if(x<0){putchar('-');x=-x;}
  if(x>9){print(x/10);putchar(x%10+'0');}
  else putchar(x+'0');
  return;
}

const int N = 5e5 + 10;

int n, a[N], d[N], last[N], q, ans[N], tt[N], lr;

vector<int> v; 

int find(int x) {
  return lower_bound(v.begin(), v.end(), x) - v.begin() + 1;
} 

struct Segtree {
  int l, r, mi;
} t[N << 2]; 

struct Node {
  int l, r, id;
  bool operator < (const Node &x) const {
    if(x.r != r) return x.r < r;
    return x.l < l;
  }
} Q[N];

void pushup(int p) {
  t[p].mi = min(t[ls].mi, t[rs].mi);
}

void build(int p, int l, int r) {
  t[p].l = l, t[p].r = r;
  if(l == r) {
    t[p].mi = d[l];
    return ;
  }
  int mid = (l + r) >> 1;
  build(ls, l, mid);
  build(rs, mid + 1, r);
  pushup(p);
}

int query(int p, int l, int r) {
  if(l <= t[p].l && t[p].r <= r) {
    return t[p].mi;
  } 
  int mid = (t[p].l + t[p].r) >> 1, ans = inf;
  if(l <= mid) ans = min(ans, query(ls, l, r));
  if(r > mid) ans = min(ans, query(rs, l, r));
  return ans;
}

void up(int p, int x, int v) {
  if(t[p].l == t[p].r) {
    t[p].mi = v;
    return ;
  }
  int mid = (t[p].l + t[p].r) >> 1;
  if(x <= mid) up(ls, x, v);
  else up(rs, x, v);
  pushup(p); 
}

signed main() {
//  freopen("jewel.in", "r", stdin);
//  freopen("jewel.out", "w", stdout);
  n = read(), q = read();
  For(i,1,n) a[i] = read(), v.push_back(a[i]);
  sort(v.begin(), v.end());
  v.erase(unique(v.begin(), v.end()), v.end());
  FOR(i,n,1) last[i] = inf;
  FOR(i,n,1) d[i] = (last[find(a[i])] == inf ? inf : last[find(a[i])] - i), last[find(a[i])] = i;
  For(i,1,n) last[i] = inf;
  For(i,1,n) tt[i] = (last[find(a[i])] == inf ? inf : last[find(a[i])]), last[find(a[i])] = i;
  For(i,1,q) Q[i] = (Node) {read(), read(), i};
  sort(Q + 1, Q + q + 1);
  build(1, 1, n);
  lr = n;
  For(i,1,q) {
    int l = Q[i].l, r = Q[i].r;
    if(lr == r) ans[Q[i].id] = query(1, l, r);
    else {
      int L = r + 1, R = lr;
      For(j,L,R) {
        if(tt[j] != inf) up(1, tt[j], inf);
      }
      ans[Q[i].id] = query(1, l, r);
    } 
    lr = r;
  }
  For(i,1,q) {
    cout << (ans[i] == inf ? -1 : ans[i]) << '\n';
  }
  return 0; 
}