HDU 暑假多校 2023 第四场

发布时间 2023-07-28 10:37:24作者: Luckyblock

写在前面

补题地址:https://acm.hdu.edu.cn/listproblem.php?vol=64,题号 7312~7323。

我是飞舞。

小子们哪,你们要自守,远避偶像。
Dear children, keep yourselves from idols.
——约翰一书

以下按照个人向难度排序。

7317

签到,特判 \(n = 2\)

//
/*
By:Luckyblock
*/
#include <cmath>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#define LL long long
//=============================================================
//=============================================================
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;
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  int T = read();
  while (T --) {
    int n = read();
    double ans1 = 0, ans2 = 2.0;
    if (n == 2) {
      ans1 = 1.0, ans2 = 1.0;
      printf("%.9lf %.9lf\n", ans1, ans2);
      continue;
    }
    LL k = 1ll * n * (n - 1) / 2;
    ans1 = (n - 1) + 2.0 * (k - n + 1);
    ans1 /= 1.0 * k;
    printf("%.9lf %.9lf\n", ans1, ans2);
  }
  return 0;
}

7323

发现 Alice 取走一个物品可以让自己获得 \(a\) 的贡献并让对方获得 \(-b\) 的贡献,Bob 取走一个物品可以让自己获得 \(b\) 的贡献并让对方获得 \(-a\) 的贡献,于是将物品按照 \(a+b\) 排序,两个人轮流按顺序拿即可。

//
/*
By:Luckyblock
*/
#include <cmath>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#define LL long long
const int kN = 1e5 + 10;
//=============================================================
int n;
struct AdmireVega {
  LL a, b, delta;
} a[kN];
//=============================================================
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;
}
bool cmp(AdmireVega fir_, AdmireVega sec_) {
  return fir_.delta > sec_.delta;
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  int T = read();
  while (T --) {
    n = read();
    for (int i = 1; i <= n; ++ i) {
      int x = read(), y = read();
      a[i] = (AdmireVega) {x, y, x + y};
    }
    std::sort(a + 1, a + n + 1, cmp);
    LL suma = 0, sumb = 0;
    for (int i = 1; i <= n; ) {
      if (i <= n) suma = suma + a[i].a, ++ i;
      if (i <= n) sumb = sumb + a[i].b, ++ i;
    } 
    printf("%lld\n", suma - sumb);
  }
  return 0;
}

7314

\(T\) 组数据,每组数据给定 \(k\) 个数列 \(a_1\sim a_k\),第 \(i\) 个数列 \(a_i\) 长度为 \(c_i\)。现要求从每个数列中选择一个数构成一大小为 \(k\) 的集合 \(b\),要求最小化 \(\max\{b_i\} - \min\{b_i\}\)
\(1\le T\le 10^6\)\(1\le k\le 10^6\)\(\sum c_i\le 10^6\)\(\sum \sum c_i\le 4\times 10^6\)
3S,256MB。

学到许多。

很显然的想法是枚举 \(m = \max\{b_i\}\) 并且最大化 \(\min\{b_i\}\)。但是发现我们无法在原有的 \(k\) 个数列的结构上快速检查是否可以从每个数列中选择出最大的不大于 \(m\) 的数。

于是不得不考虑换一种存储结构。发现我们仅关注能否从每个数列中选择一个不大于 \(m\) 的数并最大化这个数的最小值,意味着我们可以把那些介于这两个值之间的数也全部看做已选择,即从每个数列中选择的个数 \(\ge 1\)

于是考虑对于所有数,记录他们位于的数列的编号,再合并成一个有序数列。然后考虑尺取法,枚举滑动窗口的右端点,即枚举 \(m = \max\{b_i\}\),保证滑动的窗口中每个数列中的数至少有一个的情况下单调右移左端点即可最大化 \(\min\{b_i\}\)

总时间复杂度 \(O\left(T\sum c_i\log (\sum c_i)\right)\) 级别。

//
/*
By:Luckyblock
*/
#include <cmath>
#include <cstdio>
#include <cctype>
#include <vector>
#include <cstring>
#include <algorithm>
#define pr std::pair
#define mp std::make_pair
const int kN = 1e6 + 10;
const int kInf = 2e9 + 2077;
//=============================================================
int k, num, sumc, cnt[kN];
int ans;
std::vector <pr <int, int> > a;
//=============================================================
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 Init() {
  ans = kInf;
  num = sumc = 0;
  a.clear();

  k = read();
  for (int i = 1; i <= k; ++ i) {
    cnt[i] = 0;

    int c = read();
    sumc += c;
    for (int j = 1; j <= c; ++ j) {
      int x = read();
      a.push_back(mp(x, i));
    }
  }
  std::sort(a.begin(), a.end());

}
void Solve() {
  int l = 0, r = -1;
  while (num < k) {
    ++ r;
    if (!cnt[a[r].second]) ++ num;
    cnt[a[r].second] ++;
  }
  while (l <= r) {
    if (cnt[a[l].second] == 1) break;
    cnt[a[l].second] --;
    ++ l;
  }
  ans = std::min(ans, a[r].first - a[l].first);

  while (r < sumc - 1) {
    ++ r;
    cnt[a[r].second] ++;
    while (l <= r) {
      if (cnt[a[l].second] == 1) break;
      cnt[a[l].second] --;
      ++ l;
    }
    ans = std::min(ans, a[r].first - a[l].first);
  }
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  int T = read();
  while (T --) {
    Init();
    Solve();
    printf("%d\n", ans);
  }
  return 0;
}

7321

呃呃手玩题。

只有一个人就是容易被手玩题薄纱。

钦定 \(n\le m\),首先当 \(n=1\) 时,答案即 \(\left\lfloor \frac{m+1}{2} \right\rfloor\)

对于 \(n\ge 2\),发现可以在大多数情况下没有其他影响地消掉一个 \(1\times 3\) 的结构,即 \(n\times m\) 的结构可以转化为 \(n\times (m-3)\) 的结构1。大力手玩之后发现所有情况都可以变为 \(1\le n\le m\le 3\) 的情况。

于是特判一下即可。详见代码。

//
/*
By:Luckyblock
*/
#include <cmath>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
//=============================================================
//=============================================================
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;
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  int T = read();
  while (T --) {
    int n = read(), m = read(), ans = 0;
    if (n > m) std::swap(n, m);
    if (n == 1) {
      ans = (m + 1) / 2;
    } else if (n == 2) {
      if (m % 3) ans = 1;
      else ans = 2;
    } else {
      if (n % 3 == 0 || m % 3 == 0) ans = 2;
      else ans = 1;
    }
    printf("%d\n", ans);
  }
  return 0;
}

7322

7318

写在最后

学到了什么

  • 从每个集合中选一个有时可以转化为从每个集合选不小于 1 个,根据单调性可以变成至少选若干种的序列问题。
  • 手玩题扔给队友做。