【题解】CF1154

A

Description

有三个整数 a, b, c,现在给定 x1 = a+b, x2 = a+c,x3 = b+c, x4 = a+b+c,请求出 a, b, c 分别是多少。输入数据是乱序的。

Limitation

 i  [1, 4], 2  xi  109

数据保证有解。

Solution

考虑由于 x1 = a+b, x4 = a+b+c,所以 c=a+b+c(a+b)=x4x1

类似的可以得到 a, b 都可以由三个数字的和减去另外两个数字得到。

考虑由于 a, b, c 都是正整数,所以四个数字中最大的显然是三个数字的和,用这个数分别减去前三个数字就是答案了。

Code

ll a[4];

int main() {
  freopen("1.in", "r", stdin);
  for (auto &i : a) qr(i);
  std::sort(a, a + 4);
  qw(a[3] - a[0], ' ', true); qw(a[3] - a[1], ' ' , true); qw(a[3] - a[2], '\n', true);
  return 0;
}

B

Description

给定一个长度为 n 的序列,请你选择一个非负整数 D,然后将序列中的所有元素要么加上 D,要么减去 D,要么不做改变,使得修改后的序列所有元素都相等。最小化 D

Limitation

1  n  100,  i  [1,n], 1  ai  100

Solution

考虑一个显然的事实是 0  D  100

证明上考虑如果 D > 100,由于 ai  100,则所有的 ai 都不可能减掉 D。同时如果存在一个 aj 使得 aj 的操作是加上 D 的话,那么其他所有 a 的操作都一定是加上 D。因为他们初始都小于 100,但是 aj 操作后的值大于 100,所以必须都加上 D。由此导出所有的 a 都相同,那么 D=0 显然优于 D > 100。另外如果没有任何一个 ai 的操作是加上 D 的话,那么 D=0 显然仍然是最优的。

于是我们考虑枚举 D。考虑第一个位置只有 a1D, a1, a1+D 三种可能,将这种情况分别向后枚举看是否合法即可。

Code

const int maxn = 109;

int n;
int MU[maxn];

void check(int x, int k);

int main() {
  freopen("1.in", "r", stdin);
  qr(n);
  for (int i = 1; i <= n; ++i) qr(MU[i]);
  int ans = 0;
  while (ans <= 100) {
    check(MU[1], ans);
    check(MU[1] - ans, ans);
    check(MU[1] + ans, ans);
    ++ans;
  }
  puts("-1");
  return 0;
}

void check(int x, int k) {
  if (x < 0) return;
  for (int i = 1; i <= n; ++i) {
    if ((MU[i] != x) && (abs(MU[i] - x) != k)) return;
  }
  qw(k, '\n', true);
  exit(0);
}

C

Description

Polycarp 有一只小猫,这只猫饮食上有如下规律:

在每周的周一、周四、周日,猫咪会吃一条鱼

在每周的周二、周六,猫咪会吃一份兔肉(兔兔这么可爱QAQ)

剩下的时间猫咪会吃一份鸡肉

现在 Polycarp 要带这猫出去旅行,他会带 a 条鱼, b 份兔肉,c 份鸡肉。

假如他采取最优的策略来决定在一周的哪一天出发,请问他带的这些食物最多可以喂饱这只猫几天。

Limitation

1  a, b, c  7 × 108

Solution

考虑中间的整周,显然每周消耗 3 条鱼,2 份兔肉,2 份鸡肉。于是整周的个数是 min(a/3, b/2, c/2)

然后将整周去掉,考虑枚举从哪一天出发,然后向后枚举能喂几天。当一种食物减成负数则停止。考虑到把 a, b, c 分别除掉整周消耗后,一定有一种食物是小于等于 3 的。于是这样的枚举是常数级别的,可以通过。

Code

#include <cstdio>
#include <algorithm>
#ifdef ONLINE_JUDGE
#define freopen(a, b, c)
#endif

typedef long long int ll;

namespace IPT {
  const int L = 1000000;
  char buf[L], *front=buf, *end=buf;
  char GetChar() {
    if (front == end) {
      end = buf + fread(front = buf, 1, L, stdin);
      if (front == end) return -1;
    }
    return *(front++);
  }
}

template <typename T>
inline void qr(T &x) {
  char ch = IPT::GetChar(), lst = ' ';
  while ((ch > '9') || (ch < '0')) lst = ch, ch=IPT::GetChar();
  while ((ch >= '0') && (ch <= '9')) x = (x << 1) + (x << 3) + (ch ^ 48), ch = IPT::GetChar();
  if (lst == '-') x = -x;
}

namespace OPT {
  char buf[120];
}

template <typename T>
inline void qw(T x, const char aft, const bool pt) {
  if (x < 0) {x = -x, putchar('-');}
  int top=0;
  do {OPT::buf[++top] = static_cast<char>(x % 10 + '0');} while (x /= 10);
  while (top) putchar(OPT::buf[top--]);
  if (pt) putchar(aft);
}

ll a, b, c, ans;

int main() {
  freopen("1.in", "r", stdin);
  qr(a); qr(b); qr(c);
  ans = std::min(a / 3, std::min(b / 2, c / 2));
  a -= 3 * ans; b -= ans << 1; c -= ans << 1;
  ans *= 7;
  ll tt = 0;
  for (int i = 1; i <= 7; ++i) {
    ll ta = a, tb = b, tc = c;
    ll ts = 0;
    int day = i;
    while (true) {
      if ((day == 1) || (day == 4) || (day == 7)) {
        if (ta-- == 0) break;
      } else if ((day == 2) || (day == 6)) {
        if (tb-- == 0) break;
      } else if (tc-- == 0) break;
      ++day; ++ts;
      if (day == 8) day = 1;
    }
    tt = std::max(tt, ts);
  }
  qw(ans + tt, '\n', true);
  return 0;
}

D

Description

在一个数轴上,有一个机器人要从 x=0 处移动到 x=n 处。机器人身上有两种电池,第一种是普通电池,第二种是太阳能可充电电池,普通电池的容量上限为 b 点电量,太阳能电池的容量上限为 a 点电量。

定义数轴上的第 i 段线段代表左端点为 x=i1,右端点为 x=i 的线段。

n 条线段中,有一些线段可以被太阳照射到。

当机器人向右移动一个单位时,它会消耗一点电量。

当机器人走到一个可以被太阳照射到的线段上时,如果他是使用普通电池走到这条线段的并且太阳能电池的电量不满,则可以增加一点电量。这里的线段特指长度为 1 的线段。即如果它从一条被照射到的线段上走到另一条被照射的线段上,依然有可能增加电量。

机器人总电量为 0 或到达终点时会停下。现在请你求出机器人最远可以走多远。

Limitation

1  n, b, a  2 × 105

 i  [1, n], 0  si  1

Solution

考虑贪心。一个显然的事实是不考虑充电时在任意时刻机器人剩的总电量越多走的就越远。那么我们考虑优先使用太阳能电池,因为太阳能电池有恢复的可能。需要充电时会消耗普通电池的能量,于是不充电时优先使用太阳能电池不会更劣。

考虑走到一个能充电的格子上,如果太阳能电池是满的则不能充电,这时用太阳能电池同上可以证明不会更劣。考虑如果不是满的,则贪心的将电冲上。因为充电后电的总量不会减少但是可消耗的太阳能电池能量变多了。

更加严谨的证明可以考虑在可以充电的位置使用太阳能电池不会让答案更优。

于是 O(n) 的扫描整个数轴即可。

Code

#include <cstdio>
#ifdef ONLINE_JUDGE
#define freopen(a, b, c)
#endif

typedef long long int ll;

namespace IPT {
  const int L = 1000000;
  char buf[L], *front=buf, *end=buf;
  char GetChar() {
    if (front == end) {
      end = buf + fread(front = buf, 1, L, stdin);
      if (front == end) return -1;
    }
    return *(front++);
  }
}

template <typename T>
inline void qr(T &x) {
  char ch = IPT::GetChar(), lst = ' ';
  while ((ch > '9') || (ch < '0')) lst = ch, ch=IPT::GetChar();
  while ((ch >= '0') && (ch <= '9')) x = (x << 1) + (x << 3) + (ch ^ 48), ch = IPT::GetChar();
  if (lst == '-') x = -x;
}

namespace OPT {
  char buf[120];
}

template <typename T>
inline void qw(T x, const char aft, const bool pt) {
  if (x < 0) {x = -x, putchar('-');}
  int top=0;
  do {OPT::buf[++top] = static_cast<char>(x % 10 + '0');} while (x /= 10);
  while (top) putchar(OPT::buf[top--]);
  if (pt) putchar(aft);
}

const int maxn = 200010;

int n, a, b;
int MU[maxn];

int main() {
  freopen("1.in", "r", stdin);
  qr(n); qr(a); qr(b);
  int x = a, y = b;
  for (int i = 1; i <= n; ++i) qr(MU[i]);
  for (int i = 1; i <= n; ++i) {
    if ((x | y) == 0) {
      qw(i - 1, '\n', true);
      return 0;
    } else {
      if (MU[i]) {
        if ((y == b) || (x == 0)) {
          --y;
        } else {
          --x; ++y;
        }
      } else {
        if (y == 0) --x;
        else --y;
      }
    }
  }
  qw(n, '\n', true);
  return 0;
}

E

Description

n 个人站成一排,他们每个人都有一个能力值,能力值互不相同。

有两个教练,分别是 1 队教练和 2 队教练。由 1 队教练先挑,每次教练会将场上剩下的人中能力值最高的和他左右各 k 个人从场上挑出,加入自己的队伍,然后由另一名教练再挑。

在挑选时如果一侧不足 k 个人则将这些人都挑入队伍。

请求出每个人最终加入的是哪个队伍

Limitation

1  k  n  2 × 105

Solution

考虑先将每个人的位置按照他们的能力值排序,然后配合一个是否被删除的标记数组就可以均摊 O(1) 的去确定每个状态下能力值最高的人在哪个位置了。

考虑将自己和左右 k 个人选出的操作可以使用双向链表完成。由于每个人只会被删除一次,所以总复杂度 O(n)

再删除的时候可以顺手标记上这个人已经被删除了,如果求能力值最高的人时该人被删除,则查找下一个即可。

加上排序总复杂度 O(nlogn)

Code

#include <cstdio>
#include <algorithm>
#ifdef ONLINE_JUDGE
#define freopen(a, b, c)
#endif

typedef long long int ll;

namespace IPT {
  const int L = 1000000;
  char buf[L], *front=buf, *end=buf;
  char GetChar() {
    if (front == end) {
      end = buf + fread(front = buf, 1, L, stdin);
      if (front == end) return -1;
    }
    return *(front++);
  }
}

template <typename T>
inline void qr(T &x) {
  char ch = IPT::GetChar(), lst = ' ';
  while ((ch > '9') || (ch < '0')) lst = ch, ch=IPT::GetChar();
  while ((ch >= '0') && (ch <= '9')) x = (x << 1) + (x << 3) + (ch ^ 48), ch = IPT::GetChar();
  if (lst == '-') x = -x;
}

namespace OPT {
  char buf[120];
}

template <typename T>
inline void qw(T x, const char aft, const bool pt) {
  if (x < 0) {x = -x, putchar('-');}
  int top=0;
  do {OPT::buf[++top] = static_cast<char>(x % 10 + '0');} while (x /= 10);
  while (top) putchar(OPT::buf[top--]);
  if (pt) putchar(aft);
}

const int maxn = 200009;

int n, k, cot;
int MU[maxn], pos[maxn], lf[maxn], rf[maxn], ans[maxn];
bool dlt[maxn];

inline bool cmp(const int _a, const int _b) {
  return MU[_a] > MU[_b];
}

int main() {
  freopen("1.in", "r", stdin);
  qr(n); qr(k);
  for (int i = 1; i <= n; ++i) qr(MU[i]);
  for (int i = 1; i <= n; ++i) pos[i] = i;
  std::sort(pos + 1, pos + 1 + n, cmp);
  for (int i = 1; i <= n; ++i) {
    lf[i] = i - 1; rf[i] = i + 1;
  } rf[n] = 0;
  for (int i = 1; i <= n; ++i) if (!dlt[pos[i]]) {
    if (cot != 1) cot = 1;
    else cot = 2;
    int s = lf[pos[i]];
    int cnt = 0;
    while ((cnt < k) && (s)) {
      dlt[s] = true;
      ans[s] = cot;
      ++cnt;
      if (lf[s]) rf[lf[s]] = rf[s];
      if (rf[s]) lf[rf[s]] = lf[s];
      s = lf[s];
    }
    s = rf[pos[i]];
    cnt = 0;
    while ((cnt < k) && (s)) {
      dlt[s] = true;
      ans[s] = cot;
      ++cnt;
      if (lf[s]) rf[lf[s]] = rf[s];
      if (rf[s]) lf[rf[s]] = lf[s];
      s = rf[s];
    }
    s = pos[i];
    if (lf[s]) rf[lf[s]] = rf[s];
    if (rf[s]) lf[rf[s]] = lf[s];
    ans[pos[i]] = cot; dlt[pos[i]] = true;
  }
  for (int i = 1; i <= n; ++i) qw(ans[i], ' ', false);
  putchar('\n');
  return 0;
}

F

Description

商店里有 n 双鞋,每双鞋都有一个价格。你要买其中的严格 k 双。每双鞋只能被买一次。

你每次购买可以挑选剩余鞋中的任意一个子集来购买集合中所有的鞋。

m 种套餐,第 i 种套餐代表如果一次性购买 xi 双鞋则其中最便宜的 yi 双免费。

m 种套餐每种都可以用任意次。

现在请求出买严格 k 双鞋的最小化费。

Limitation

1  n, m 2 × 105

1  k  min(n,2000)

1  ai  2 × 105

Solution

这tm是个假数据范围……

我们考虑鞋子是无序的,于是我们将他们按照升序排序。

考虑如果要最小化花费,那么只可能买最便宜的前 k 双鞋。证明上考虑调整法,将某一双鞋换成另外的一双,如果这双鞋在最终方案中是需要花钱购买的,则答案更劣,否则答案不变,于是证明了换成更贵的不会使答案更优

在考虑对于 x 相同的套餐,显然免费数量 y 越高越高,于是我们对每个 x 记录一个最大的 y,即为 cx

接下来考虑我们买鞋可以分为两个部分,第一部分是按照原价买的,第二部分是套餐部分。由于套餐部分有一些鞋子是免费的,我们当然希望被免费的价格越高越好,于是我们要贪心的将套餐往后放,那么我们只会用原价购买最便宜的一部分鞋子,即从 1 开始连续到某个位置的鞋子是原价购买的。剩下的全部使用套餐购买。

现在考虑如果将最后的 i 双鞋子使用套餐购买,如何最小化答案:

这个东西可以使用DP完成,设 fi 为最后 i 双鞋使用套餐购买的最小值,转移可以枚举使用 x 为多大的套餐,于是状态转移方程为

fi = maxj=0i1{fj + calc(i,j)}

其中 calc(i,j) 代表区间 [ki,kj] 的鞋使用的套餐购买的最小花费,计算方法为

calc(i,j) = s=kikj ass=kiki+cjias

也就是这段区间的价格和减去最大的优惠值。

通过求 a 的前缀和,calc 可以 O(1) 计算。于是DP的总时间复杂度 O(k2)

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#ifdef ONLINE_JUDGE
#define freopen(a, b, c)
#endif

typedef long long int ll;

namespace IPT {
  const int L = 1000000;
  char buf[L], *front=buf, *end=buf;
  char GetChar() {
    if (front == end) {
      end = buf + fread(front = buf, 1, L, stdin);
      if (front == end) return -1;
    }
    return *(front++);
  }
}

template <typename T>
inline void qr(T &x) {
  char ch = IPT::GetChar(), lst = ' ';
  while ((ch > '9') || (ch < '0')) lst = ch, ch=IPT::GetChar();
  while ((ch >= '0') && (ch <= '9')) x = (x << 1) + (x << 3) + (ch ^ 48), ch = IPT::GetChar();
  if (lst == '-') x = -x;
}

namespace OPT {
  char buf[120];
}

template <typename T>
inline void qw(T x, const char aft, const bool pt) {
  if (x < 0) {x = -x, putchar('-');}
  int top=0;
  do {OPT::buf[++top] = static_cast<char>(x % 10 + '0');} while (x /= 10);
  while (top) putchar(OPT::buf[top--]);
  if (pt) putchar(aft);
}

const int maxn = 200009;
const int maxt = 2005;

int n, m, k;
ll ans;
int MU[maxn], CU[maxn];
ll frog[maxn], presum[maxn];

int main() {
  freopen("1.in", "r", stdin);
  qr(n); qr(m); qr(k);
  for (int i = 1; i <= n; ++i) qr(MU[i]);
  std::sort(MU + 1, MU + 1 + n);
  for (int i = 1, a, b; i <= m; ++i) {
    a = b = 0; qr(a); qr(b); CU[a] = std::max(CU[a], b);
  }
  memset(frog, 0x3f, sizeof frog); frog[0] = 0;
  for (int i = 1; i <= k; ++i) presum[i] = presum[i - 1] + MU[i];
  ans = presum[k];
  for (int i = 1; i <= k; ++i) {
    for (int j = i - 1; ~j; --j) {
      int s = i - j;
      frog[i] = std::min(frog[i], frog[j] + presum[k - j] - presum[k - i + CU[s]]);
    }
    ans = std::min(ans, frog[i] + presum[k - i]);
  }
  qw(ans, '\n', true);
  return 0;
}

G

Description

给定一个长度为 n 的序列 a,找出两个数,最小化他们的最小公倍数

Limitation

2  n  106

1  ai  107

Solution

考虑式子

x × y = gcd(x, y) × lcm(x, y)

于是有

lcm(x, y) = x × ygcd(x, y)

考虑由于值域在 1e7 范围内,于是任意两数的 gcd 也在 1e7 范围内。我们考虑枚举这个 gcd,然后枚举 gcd 的倍数是否出现在给定的数列中,如果有两个以上的倍数出现在给定的数列中,则取最小的两个,求一下 lcm,看一下是否能更新答案。取最小的两个的原因是考虑上式中分母不变,分子越小则 lcm 越小,于是取较小的更优。又由于 1e7 范围内的 gcd 包含了所有的情况,于是这样的正确性是对的。

考虑如果枚举到 x,数列中出现最小的两个 x 的倍数是 yz,若 x 只是 yz 的因数,但不是最大的怎么办:考虑枚举到 gcd(y, z) 时依然可以更新答案,由于 x<gcd(y, z),所以用 x 求出的答案要大于用 gcd(y, z) 求出的答案,错误的答案会被正确的更新掉。

于是枚举每个数的倍数单次复杂度 O(logN),于是总复杂度 O(N logN),其中 N 为值域。

Code

#include <cstdio>
#include <algorithm>
#ifdef ONLINE_JUDGE
#define freopen(a, b, c)
#endif

typedef long long int ll;

namespace IPT {
  const int L = 1000000;
  char buf[L], *front=buf, *end=buf;
  char GetChar() {
    if (front == end) {
      end = buf + fread(front = buf, 1, L, stdin);
      if (front == end) return -1;
    }
    return *(front++);
  }
}

template <typename T>
inline void qr(T &x) {
  char ch = IPT::GetChar(), lst = ' ';
  while ((ch > '9') || (ch < '0')) lst = ch, ch=IPT::GetChar();
  while ((ch >= '0') && (ch <= '9')) x = (x << 1) + (x << 3) + (ch ^ 48), ch = IPT::GetChar();
  if (lst == '-') x = -x;
}

namespace OPT {
  char buf[120];
}

template <typename T>
inline void qw(T x, const char aft, const bool pt) {
  if (x < 0) {x = -x, putchar('-');}
  int top=0;
  do {OPT::buf[++top] = static_cast<char>(x % 10 + '0');} while (x /= 10);
  while (top) putchar(OPT::buf[top--]);
  if (pt) putchar(aft);
}

const int maxn = 1000009;
const int maxt = 10000009;
const int upceil = 10000000;

int n, x, y;
ll ans = ((-1ull) << 1) >> 1;
int id[maxt];

int main() {
  freopen("1.in", "r", stdin);
  qr(n);
  for (int i = 1, k; i <= n; ++i) {
    k = 0; qr(k);
    if (id[k] && k < ans) {
      ans = k; x = id[k]; y = i;
    }
    id[k] = i;
  }
  for (int i = 1; i <= upceil; ++i) {
    int a = 0;
    for (int j = i; j <= upceil; j += i) if (id[j]) {
      if (a == 0) {
        a = j;
      } else {
        ll lcm = 1ll * a * j / i;
        if (lcm < ans) {
          x = id[a]; y = id[j]; ans = lcm;
        }
      }
    }
  }
  if (x > y) std::swap(x, y);
  qw(x, ' ', true); qw(y, '\n', true);
  return 0;
}
posted @   一扶苏一  阅读(714)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示