「联合省选 2020 A | B」冰火战士

知识点:

原题面 Loj Luogu


考场上写了二分 + 树状数组。
没想到可以再二分一次于是加了个 set,水了 60。

当时还不知道线段树上二分这种傻逼玩意= =
今日学到虚脱。


题意简述

简不动,简不动。


分析题意

读完题发现全是废话。可总结出下面几个结论:

  1. 将冰火人分别按温度升序排序,冰人选择的是一段前缀,火人选择的是一段后缀。
    消耗的能量即 选择的冰人能量总和 和 火人能量总和 较小的的一方的两倍。
  2. 选择的温度一定是某个人的温度。

由结论 2,考虑先离散化温度,以下讨论中的温度均指离散化后的温度。

由结论 1,考虑单调性。
前缀和随温度递增,后缀和随温度递减,消耗的能量为前后缀较小的一方,显然为一关于温度的单峰函数。


想到二分温度,用树状数组求得前后缀和检查,取最优答案。
令能量值最大的 温度可能有多个,注意输出的是最大温度,可二分得到。
复杂度 \(O(Q\log^2 Q)\),期望得分 \(60pts\)


过不去,考虑科技。
\(\operatorname{sum}_{ice}(x, y)\) 表示冰人在温度 \([x,y]\) 内的能量和,同理有 \(\operatorname{sum}_{fie}(x,y)\)
对于最优的温度 \(pos\),。

补集转化,火人的贡献是一段后缀,可看做全部减去一段前缀,两方贡献均可转化为前缀的形式。
分别对冰火人的能量 维护 两个树状数组,
结合树状数组的本质是二进制优化的前缀和,考虑在树状数组上二分最大的令 冰人能量和 小于 火人能量和的温度(个人感觉更像倍增)。

具体地,记 \(\operatorname{len}\) 为当前倍增到的,最大的合法温度。
降序枚举区间长度 \(l\),每次检查 \(\operatorname{sum}_{ice}(1, len + l)\) 是否 \(<\operatorname{sum}_{fire}(len + l, n)\)
满足则令 \(len + l\),否则跳过。

由树状数组结构可知,第 \(len + l\) 个节点代表的 \(\operatorname{sum}\) 即为 \(\operatorname{sum}(len,l+l)\)


最后得到这样的位置 \(pos\),满足 \(sum_{ice}(1, pos) < sum_{fire}(pos,n)\)
而最优的答案可能满足 \(sum_{ice}(1, pos) > sum_{fire}(pos,n)\),即火人的能量先耗光。

发现这种情况一定是 温度为 \(pos+1\) 的答案。
再二分得到最大的 符合这种情况 的温度即可。


上述描述仅代表大致思路。
实现时有一些奇怪的边界问题和细节,可能与上述描述有出入。
在代码中会详细指出。


爆零小技巧

为什么线段树是四倍常数?
考虑一次区间查询,遍历过的线段树的节点 组成的查询树。
查询区间会被查询的区间分为 \(\log n\) 个完整的,节点维护的区间,在树上体现为 \(\log n\) 个叶节点。
由线段树完美二叉树的结构,则查询树也会有 \(4\log n\) 级别的节点数。
每次查询都会遍历 \(4\log n\) 个节点,故为 \(4\) 倍常数。


代码实现

//知识点:树状数组上二分 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cstdio>
#include <ctype.h>
#include <cstring>
#define ll long long
#define lowbit(x) (x&-x)
const int kMaxn = 2e6 + 10;
//=============================================================
struct Que {
  int opt, t, x, y, k;
} q[kMaxn];
int data_num, data[kMaxn];
int allfire, pos, ans;
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void GetMax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
void GetMin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
struct BitTree {
  int t[kMaxn];
  void Modify(int pos_, int val_) {
    for (; pos_ < kMaxn; pos_ += lowbit(pos_)) t[pos_] += val_;
  }
  int Query(int pos_) {
    int ret = 0;
    for (; pos_; pos_ -= lowbit(pos_)) ret += t[pos_];
    return ret;
  }
} ice, fire;

void Prepare(int Q) {
  for (int i = 1; i <= Q; ++ i) {
    q[i].opt = read();
    if (q[i].opt == 1) {
      q[i] = (Que) {q[i].opt, read(), read(), read()}; 
      data[++ data_num] = q[i].x;
    } else {
      q[i].k = read(); 
    }
  }
  std :: sort(data + 1, data + data_num + 1);
  data_num = std :: unique(data + 1, data + data_num + 1) - data - 1;
  for (int i = 1; i <= Q; ++ i) {
    if (q[i].opt) q[i].x = std :: lower_bound(data + 1, data + data_num + 1, q[i].x) - data; 
  }
}
void Query() {
  //len 为当前倍增到的,最大的合法温度。
  //ice_psum,fire_psum 分别为 冰火人的前缀和。
  int len = 0, ice_psum = 0, fire_psum = 0;
  for (int i = 20; i >= 0; -- i) { //枚举区间长度倍增。
    int l = (1 << i);
    //注意 icesum 的范围是 [1, len]  
    //而 firesum 的范围是 [len + 1, n]  
    //firesum 虽不满足大于等于 len 的条件,
    int icesum = ice_psum + ice.t[len + l];
    int firesum = allfire - fire_psum - fire.t[len + l];
    if (len + l <= data_num && icesum < firesum) {
      len += l;
      ice_psum += ice.t[len],  fire_psum += fire.t[len];
    }
  }

  //ans1为温度为 len 的消耗,ans2为温度为len + 1 的消耗。  
  //可知 ans1 与 ans2 一定不等,否则在倍增时会倍增到 len + 1。  
  int ans1 = ice_psum; //温度为 len 时一定 ice 更小

  //温度为 len + 1 时,len + 1 为火,则 ice 更小,否则 fire 更小。
  //且温度取 len + 1 的答案一定比取 len + 2 优,对 len + 1 的属性分类讨论,列出不等式可知。
  int ans2 = std :: min(ice.Query(len + 1), allfire - fire_psum); 
  if (ans1 > ans2) {
    pos = len, ans = ans1;
    return ;
  }
  //温度取 len + 1 更优,倍增找到 最大的消耗相同的温度。  
  //倍增时多一重判断,其他部分均相同。
  ans = ans2;
  len = 0, ice_psum = 0, fire_psum = 0;
  for (int i = 20; i >= 0; -- i) {
    int l = (1 << i);
    int newlsum = ice_psum + ice.t[len + l];
    int newrsum = allfire - fire_psum - fire.t[len + l];
    if (len + l <= data_num && 
        (newlsum < newrsum || std :: min (newlsum, newrsum) == ans)) {
      len += l;
      ice_psum += ice.t[len],  fire_psum += fire.t[len];
    }
    pos = len;
  }
}
//=============================================================
int main() {
  int Q = read();
  Prepare(Q);
  for (int i = 1; i <= Q; ++ i) {
    int opt = q[i].opt, t, x, y, k;
    if (opt == 1) {
      t = q[i].t, x = q[i].x, y = q[i].y;
      if (! t) ice.Modify(x, y);
      else fire.Modify(x, y), allfire += y;
    } else {
      int k = q[i].k;
      t = q[k].t, x = q[k].x, y = q[k].y;
      if (! t) ice.Modify(x, - y);
      else fire.Modify(x, - y), allfire -= y;
    }
    Query();
    if (ans) printf("%d %d\n", data[pos + 1], 2 * ans);
    else printf("Peace\n");
  }
  return 0;
}

这里是自己 YY 的的的线段树二分。
没写完测了测,T 了就弃了。
还差一步 找到最大的相等的一段。
人傻常数大没办法,可能加上离散化就过了(((

//知识点:线段树,二分
/*
By:Luckyblock
*/
#include <algorithm>
#include <cstdio>
#include <ctype.h>
#include <cstring>
#include <map>
#define ll long long
#define ls (lson[now_])
#define rs (rson[now_])
#define allfire (sum[1][1])
const int kMaxn = 2e6 + 10;
const int kInf = 2e9;
//=============================================================
int Q, sumfire, sumice, ans1, ans2, q[kMaxn][3];
//0 冰 1 火 
int root, node_num, sum[kMaxn << 2][2], lson[kMaxn << 2], rson[kMaxn << 2];
std :: map <int, int> fire;
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void GetMax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
void GetMin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
void Pushup(int now_) {
  sum[now_][0] = sum[ls][0] + sum[rs][0];
  sum[now_][1] = sum[ls][1] + sum[rs][1];
}
void Modify(int &now_, int L_, int R_, int type, int pos_, int val_) {
  if (! now_) now_ = ++ node_num;
  sum[now_][type] += val_;
  if (L_ == R_) return ;
  int mid = (L_ + R_) >> 1;
  if (pos_ <= mid) Modify(ls, L_, mid, type, pos_, val_);
  else Modify(rs, mid + 1, R_, type, pos_, val_);
//  Pushup(now_); 
}
int QueryPos(int now_, int L_, int R_, int sum1_, int sum2_) {
  if (L_ == R_) return sum1_ + sum[now_][0] <= sum2_ ? L_ : 0;
  int mid = (L_ + R_) >> 1, ret = 0;
  if (sum1_ + sum[ls][0] < sum2_ - sum[ls][1] + fire[mid]) { //判断 mid 合法性 
    ret = mid;
    GetMax(ret, QueryPos(rs, mid + 1, R_, sum1_ + sum[ls][0], sum2_- sum[ls][1])); 
  } else {
    GetMax(ret, QueryPos(ls, L_, mid, sum1_, sum2_)); 
  }
  return ret;
}
void QuerySum(int now_, int L_, int R_, int pos_) {
  int mid = (L_ + R_) >> 1;
  if (pos_ < mid) {
    QuerySum(ls, L_, mid, pos_);
    return ;
  }
  sumice += sum[ls][0], sumfire -= sum[ls][1];
  if (pos_ > mid) QuerySum(rs, mid + 1, R_, pos_);
}
//=============================================================
int main() {
  Q = read();
  for (int i = 1; i <= Q; ++ i) {
    int opt = read(), t, x, y;
    if (opt == 1) {
      t = q[i][0] = read(), x = q[i][1] = read(), y = q[i][2] = read();
      Modify(root, 1, kInf, t, x, y);
      if (t) fire[x] += y;
    } else {
      int k = read();
      t = q[k][0], x = q[k][1], y = q[k][2];
      Modify(root, 1, kInf, t, x, - y);
      if (t) fire[x] -= y;
    }
    int pos = ans1 = QueryPos(root, 1, kInf, 0, allfire);
    sumice = 0, sumfire = allfire + fire[pos];
    QuerySum(1, 1, kInf, pos);
    ans2 = sumice, sumice = 0, sumfire = allfire + fire[pos + 1];
    QuerySum(1, 1, kInf, pos + 1);
    if (sumfire >= ans2) {
      ans1 = pos + 1, ans2 = sumfire; 
    }
    if (! ans2) printf("Peace\n");
    else printf("%d %d\n", ans1, 2 * ans2);
  }
  return 0;
}
posted @ 2020-08-25 22:25  Luckyblock  阅读(219)  评论(3编辑  收藏  举报