设为首页|收藏本站|
开启左侧

[问答] Codeforces Round #814 (Div. 1+2) 2A~2F

[复制链接]
25771 2
度娘它舅 发表于 2022-8-19 05:23:16 | 只看该作者 打印 上一主题 下一主题
 
2A. Chip Game

题意:一个n*m的棋盘,Burenka和Tonya进行博弈,Burenka先手。开始时,棋子位于左下角(1,1),每回合,每个人可以将棋子向上移动奇数个单位,或者向右移动奇数个单位。不能移动的人判输。
题解: n是奇数的话,则竖直方向能够移动的次数肯定是偶数次,如果是偶数的话,能够移动的次数就是奇数次,因为每次移动剩余行数的奇偶性都会变。
问题其实等价于有两堆n - 1 ,m - 1的石子,每个人可以从其中一堆拿走奇数个,没有石子可取则判输。先手输当且仅当n + m - 2 是奇数。
void solve() {
  int n, m; cin >> n >> m;
  if ((n ^ m) & 1) cout << "Burenka\n";
  else cout << "Tonya\n";
}
2B. Mathematical Circus

题意:给定n, k(n为偶数),问能否将1~n分成n/2组(a_i,b_i),使得
4 \mid (a_i + k) * b_i
有解则输出YES,并输出一个解。否则输出NO。
题解:当k为奇数时,让所有奇数作为a,所有偶数作为b即可。
当k为4的倍数时,则无解。因为奇数无论放哪边,另一边必须是4的倍数,而奇数比4的倍数要多,因此无解。
当k \equiv 2 \mod 4时,有解。让所有4的倍数作为b_i和奇数配对。然后剩下的偶数作为a_i,和奇数配对。
void solve() {
  int n, k;
  cin >> n >> k;
  if (k % 4 == 0) {
    cout << "NO\n";
    return;
  }

  cout << "YES\n";
  if (k & 1) {
    for (int i = 1;i <= n;i += 2) cout << i << " " << i + 1 << "\n";
  }
  else {
    for (int i = 2;i <= n;i += 2) {
      if (i % 4 == 2) cout << i << " " << i - 1 << endl;
      else cout << i - 1 << " " << i << endl;
    }
  }
}
2C. Fighting Tournament

题意:现有水平不同的 n 人打擂台赛,按编号排成一队(1~n), 第i个人的水平为a(水平为1~n的一个排列)。
每次队首的两个人进行比试。胜者将继续站在队首,败者直接排到队尾。
有q个询问,每次询问给定两个数k, i,问前k场比赛中,编号为i的选手赢了几局?
题解: 当水平最高的人在队首时,就一直是这个人赢。而n - 1场比赛后,水平最高的人一定轮到过。因此只需先模拟n次。但是还需要保存前n - 1场每一场赢多少次,只需记录哪一场赢就好了。
void solve() {
  int n, q;
  cin >> n >> q;

  vector<int> a(n);
  cin >> a;

  deque<int> que;
  vector<vector<int>> s(n);

  for (int i = 1;i < n;i++) que.push_back(i);
  int pre = 0;
  for (int i = 1;i < n;i++) {
    auto& cur = que.front(); que.pop_front();
    if (a[pre] > a[cur]) {
      que.push_back(cur);
      s[pre].push_back(i);
    }
    else {
      que.push_back(pre);
      pre = cur;
      s[cur].push_back(i);
    }
  }

  while (q--) {
    int i, k;
    cin >> i >> k;
    --i;
    if (k < n || a != n) {
      cout << upper_bound(s.begin(), s.end(), k) - s.begin() << "\n";
    }
    else {
      cout << max(0, k + 1 - n) + (upper_bound(s.begin(), s.end(), k) - s.begin()) << "\n";
    }
  }
}
2D. Burenka and Traditions

题意:有一个数组a,每次可以选择一个区间 [l,r]和一个整数x,花费\lceil \frac{r-l+1}{2} \rceil的代价,将区间中所有数与x异或。
至少需要多少代价,数组所有元素均变为0
题解:所有的区间操作,都可以转换成以下两个操作

  • 选取一个位置i,将a和x异或
  • 选取两个相邻的位置,将这个两个位置与x异或
显然代价的上界为n,并且上面操作之间是可交换的。
我们可以考虑一个动态规划。dp 为前i个数变成0的最小代价。

  • 如果a 为0,则dp = dp[i - 1]。
  • 否则,要使得a变成0,它必然要异或一个a。并且以这个位置终止的操作次数为1次(一定存在一个最优操作方案,使得a为右端点的次数为1)。
  • 因此只有两种选择方案,选择操作1,将这个数置零,此时dp = dp[i - 1] + 1
  • 连续选择若干个操作2,如果这一段操作后,不全变成0,那跟全部进行炒作1没有区别,因此不用考虑,只需考虑操作后变成0的情况。而要变成0,必须这一段(j+1~i)的异或和为0。此时dp = dp[j] + (i - j - 1)。并且如果有多个这样的j,应该取最大更优,能减少更多操作。
综上
dp = \min(dp[i - 1] + 1, \max_{j, \oplus a[i\sim j+1]=0}dp[j] + (i - j + 1))
第二部分可以使用一个map保存每个异或和前缀最后出现的位置,就能快速得到。
void solve() {
  int n;
  cin >> n;
  vector<int> a(n + 1);
  for (int i = 1;i <= n;i++) {
    cin >> a;
  }

  map<int, int> mp;
  mp[0] = 0;

  vector<int> dp(n + 1);
  int xorSum = 0;

  for (int i = 1;i <= n;i++) {
    if (a == 0) {
      dp = dp[i - 1];
      mp[xorSum] = i;
      continue;
    }

    xorSum ^= a;
    dp = dp[i - 1] + 1;

    if (mp.count(xorSum)) {
      int j = mp[xorSum];
      dp = min(dp, dp[j] + i - j - 1);
    }
    mp[xorSum] = i;
  }
  cout << dp[n] << "\n";
}
2E. Fibonacci Strings

题意:有k种字符,第i个字符有a个。问能否用它组成一个Fibonacci字符串。即F个同样的字符按顺序拼接起来。
比如 abaabbbcccccaaaaaaaa
一项为一个a,然后1个b,接着2个a,3个b,5个c,8个a。
相邻的两项不能使用同一个字符。
题解:首先字符数总数是否为Fibonacci数列的前k项和,如果不是,那无解。如果是,那就相当于拿这些字符去填k个盒子,每个盒子只能装同一个盒子,并且相邻盒子不能使用同一个字符。
如果相邻的两项可以使用同一个字符,则可以使用贪心,每次选取剩余的字符中数目最大的去填。
为什么这样是可以的?
Fibonacci数列有以下性质:
\sum_{i}^k F + 1= F[k+2]\\
如果k为奇数
F[k] = F[1] + \sum_{i=1}^{\lfloor k/2 \rfloor } F[2i]\\
如果k为偶数
F[k] = \sum_{i=1}^{ k/2} F[2i - 1]\\
如果一个字符的数目不小于最大的那个盒子能容纳的数目。那么它必然要填充倒数第二大的那个,依此类推,它所填充的位置,必然存在某些位置的盒子大小之和等于当前最大的这个(归纳一下即可)。此时我们交换它们,最优性不变。
题目要求的是连续不能取同一个字符。我们只需在贪心的时候下一轮不考虑当前取的字符即可。我们使用一个优先队列,取完等下一轮处理完再放回优先队列即可。
为什么这样贪心是对的?
同样由上面Fibonacci数列的性质。如果我们当前轮不取它,那当前考虑的必然是Fibonacci的偶数项(否则无解),并且最大的数必须等于当前盒子大小,否则也是无解。这种情况下,也必然是从Fibonacci的第一项开始,填充所有间隔的盒子。此时它们的和等于当前盒子大小,交换一下,最优性不变。
const int N = 50;
bool inited = false;
vector<int64_t> f(N);
map<int64_t, int> mp;
void init() {
  if (inited) return;
  inited = true;
  f[0] = f[1] = 1;
  mp[1] = 0, mp[2] = 1;
  int64_t sum = 2;
  for (int i = 2; i < N; i++) {
    f = f[i - 1] + f[i - 2];
    sum += f;
    mp[sum] = i;
  }
}
void solve() {
  init();
  int n;
  cin >> n;
  vector<int> c(n);
  int64_t sum = 0;
  priority_queue<int> que;
  for (auto& x : c) {
    cin >> x;
    sum += x;
    que.push(x);
  }

  if (!mp.count(sum)) {
    cout << "NO\n";
    return;
  }

  int last = 0;
  bool success = true;
  for (int i = mp[sum]; i >= 0; i--) {
    if (que.empty()) {
      success = false;
      break;
    }
    int cur = que.top(); que.pop();
    if (cur < f) {
      success = false;
      break;
    }
    if (last) que.push(last);
    last = cur - f;
  }
  cout << (success ? "YES" : "NO") << '\n';
}

2F. Tonya and Burenka-179

题意:有一个长度为n的环形数组a,对于一个数组可以定义数组的usefulness如下:
一个固定的k,对于每个位置,可以定义usefulness为从这个位置开始,每次向前移动k步,共移动n次所经过所有位置的i数值a的和(重复经过也要算)。
而数组的usefulness为取所有k(1<=k<n),所有位置usefulness的最大值。
现在有q个操作,每个操作给定两个数i,x表示将a改为x。
每次操作后,输出当前的usefulness值。
题解:我们只需考虑k为n的因子的情况。因为取任何k和取gcd(k, n)结果都是相同的。
而对于一个因子k,就是把n个数按模n / k分成了n / k组。答案就是取和最大(平均值最大)的那一组的和乘以k。
如果有两个数k1 > k2其中k2是k1的因子。那么我们不必考虑k2。
因为k1就是把k2中的每一组,拆分成k1 / k2组。这样的话选k1肯定更优,因为总有一组的平均值大于原来的平均值。
所以我们只要考虑所有n的极大因子。
假设n的素数分解为
n = p_1^{\alpha_1}\cdots p_t^{\alpha^t}
则这些k就是n / p_i,最多只有O(\log n)个(n为素数的话,只能考虑步长为1)。这个只需\sqrt{n}就能求出来。
如果只有查询,没有修改,我们可以直接枚举k,然后暴力。
现在有一些修改操作。我们先维护一下对每个k,模k的位置的和。用两个堆维护最大值。然后就可以快速得到修改后的和了。
每个样例复杂度为O((n + q)\log n)。
void solve() {
  int n, q;
  cin >> n >> q;
  vector<int> a(n);
  cin >> a;

  vector<int> fac;
  int tmp = n;
  for (int i = 2; i <= tmp / i; i++) {
    if (tmp % i == 0) {
      fac.push_back(n / i);
      while (tmp % i == 0) tmp /= i;
    }
  }
  if (tmp != 1) fac.push_back(n / tmp);

  vector<vector<int64_t>>sum(fac.size());
  priority_queue<int64_t>que, del;

  auto addque = [&](int p, int xa) {
    for (int i = 0;i < fac.size();i++) {
      int pos = p % fac;
      del.push(sum[pos] * fac);
      sum[pos] += xa;
      que.push(sum[pos] * fac);
    }
    while (!del.empty() && del.top() == que.top()) {
      del.pop(), que.pop();
    }
  };

  for (int i = 0; i < fac.size(); i++) {
    int k = fac;
    sum.resize(k);
    for (int j = 0;j < n; j++) {
      sum[j % k] += a[j];
    }
    for (int j = 0; j < k; j++) {
      que.push(sum[j] * k);
    }
  }
  cout << que.top() << "\n";

  while (q--) {
    int p, x;
    cin >> p >> x;
    --p;
    addque(p, x - a[p]);
    a[p] = x;

    cout << que.top() << "\n";
  }
}
本文使用 Zhihu On VSCode 创作并发布



上一篇:如此强大的嘉宾阵容,也只有这场论坛才可以!
下一篇:2021年华商银行校园招聘公告!
@



1.西兔生活网 CTLIVES 内容全部来自网络;
2.版权归原网站或原作者所有;
3.内容与本站立场无关;
4.若涉及侵权或有疑义,请点击“举报”按钮,其他联系方式或无法及时处理。
 

精彩评论2

正序浏览
跳转到指定楼层
沙发
亚当那个当 发表于 2022-8-19 05:23:39 | 只看该作者
 
第二题的代码贴成第一题的了呀[潜水]
回复 支持 反对

使用道具 举报

 
板凳
sound3000 发表于 2022-8-19 05:24:28 | 只看该作者
 
[笑哭]
回复 支持 反对

使用道具 举报

 
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

排行榜
活跃网友
返回顶部快速回复上一主题下一主题返回列表APP下载手机访问
Copyright © 2016-2028 CTLIVES.COM All Rights Reserved.  西兔生活网  小黑屋| GMT+8, 2024-5-7 09:56