Loading

Codeforces Round 895 (Div. 3) 考试总结

前言

首先就是不太会打 CF,主要体现在晚上熬夜太难受,不过这场的状态还是挺好的。

然后就是时间把握不好,CCF 和模拟赛基本都是 \(3h\)\(4h\),而 CF 只有 \(2h\) 左右。所以时间很紧,像这场,明明后面的题会做也没时间做了。

赛时实况:

A B C D E F G
× × ×

赛后改题情况:

A B C D E F G

只看了 F 题的 tj,其他的都是自己改的。

A. Two Vessels

Problem

有分别装有 \(a,b\) 单位水的两个杯子,容量无线大。现在有一个勺子,容量为 \(c\),每次可以从一个杯子里舀一勺不超过 \(c\) 单位的水(单位水可以不是整数),放入另一个杯子中。请问最少需要多少次操作才能使两个杯子里的水量相同。

Solve

水题。

总水量保持不变,所以两个杯子在操作完之后的单位水量为 \(\frac{a+b}{2}\),并且 \(|a-\frac{a+b}{2}|=|b-\frac{a+b}{2}|\)。所以最后的答案为 \(\left\lceil\dfrac{|a-\frac{a+b}{2}|}{c}\right\rceil\),向上取整是因为单次舀 \(c\) 单位水最后可能填不满。

Code

#include <bits/stdc++.h>
#define ll 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;

namespace Read {
  template <typename T>
  inline void read(T &x) {
    x=0;T 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();}
    x*=f;
  }
  template <typename T, typename... Args>
  inline void read(T &t, Args&... args) {
    read(t), read(args...);
  }
}

using namespace Read;

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;
}

int T, a, b, c; 

void solve() {
  read(a, b, c);
  cout << ceil(abs(1.0 * (a + b) / 2 - a) / c) << '\n';
}

signed main() {
  read(T);
  while(T--) {
    solve();
  }
  return 0;
}

B. The Corridor or There and Back Again

Problem

有无限个房间,并且向右延伸。你现在需要从 \(1\) 号房间走到 \(k\) 号房间,并从 \(k\) 号房间回来。每向右走 \(1\) 单位需要耗费 \(1\) 单位的时间。这些房间里有些会有陷阱。共 \(n\) 个陷阱,每一个陷阱都有一个所在位置 \(d_i\) 和激活时间 \(s_i\),当你进入有陷阱的房间时,会激活陷阱,陷阱将在 \(s_i\) 秒后触发。陷阱触发后,你不能再次进入此房间或者从此房间出去。

求最远能到达的 \(k\) 号房间。

Solve

此题花费了我一点点的时间。

首先必须有去有回。当一个陷阱被出发后,你必须在某个点折返回来,并且要保证不能受陷阱影响。对于所有的陷阱,都是如此。每个陷阱都会有一个最大的,能到达并且能返回的点,将其记为 \(g_i\),答案为 \(\min\limits_{1\le i\le n} g_i\)

考虑如何计算 \(g_i\),我们将去的路和回的路拉成一条直线,这条直线的中点即为 \(g_i=\frac{(d_i+(s_i-1))}{2}\)

Code

#include <bits/stdc++.h>
#define ll 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 1e9

using namespace std;

namespace Read {
  template <typename T>
  inline void read(T &x) {
    x=0;T 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();}
    x*=f;
  }
  template <typename T, typename... Args>
  inline void read(T &t, Args&... args) {
    read(t), read(args...);
  }
}

using namespace Read;

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;
}

int T, n, d, s, k; 

void solve() {
  read(n);
  k = inf;
  For(i,1,n) {
  read(d, s);
    k = min(k, d + (s - 1) / 2);
  }
  cout << k << '\n';
}

signed main() {
  read(T);
  while(T--) {
    solve();
  }
  return 0;
}

C. Non-coprime Split

Problem

给定 \(l,r\),求是否存在满足条件的 \(a,b\),使:

  • \(l \le a+b\le r\)
  • \(\gcd(a,b)\not= 1\)

若有多组解,任意输出一组。

Solve

构造题。

要意识到一点,就是 \(\gcd(a,a)=a\)(笑),然后对着 \(a+b\le r,a=b\) 构造就行了:

  • \(r\) 为偶数, \(a=b=\frac{r}{2}\)
  • \(r\) 为奇数,\(l<r\)\(a=b=\frac{r-1}{2}\)

\(r\) 为奇数,\(l=r\)。这时候就有点麻烦了。相当于已知 \(a+b=r,\gcd(a,b)\not=1\),求 \(a,b\)

因为 \(\gcd(a,b)\not=1\),所以 \(\gcd(a,r-a)\not=1\)。根据欧几里得算法可知,\(\gcd(a,r-a)=\gcd(a,r)\not=1\),所以我们只要知道 \(a,r\) 是否有公共质因子即可。

\(a\) 是未知量,换个角度想,若 \(a\) 可以,则 \(a\) 的所有质因子也可以。由于是公共质因子,所以 \(\gcd(a,r)\) 的结果其实就是 \(r\) 的所有质因子。设 \(g\)\(r\) 的最小质因子,则 \(a=g,b=r-g\)

时间复杂度 \(O(T\sqrt n)\).

Code

#include <bits/stdc++.h>
#define ll 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 1e9

using namespace std;

namespace Read {
  template <typename T>
  inline void read(T &x) {
    x=0;T 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();}
    x*=f;
  }
  template <typename T, typename... Args>
  inline void read(T &t, Args&... args) {
    read(t), read(args...);
  }
}

using namespace Read;

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 = 1e7 + 10;

int T, l, r, p[N], len, prime[N], tot, st[N];

vector<int> v[N];

void init() {
  for (int i = 2; i <= 1e7; i++) {
    if(!st[i]) prime[++tot] = i;
    for (int j = 1; j <= tot; j++) {
      if(i * prime[j] > 1e7) break;
      st[i * prime[j]] = 1;
      if(i % prime[j] == 0) break;
    }
  }
}

void solve() {
  read(l, r);
  if(r & 1) r--;
  if(r < l) {
    r++;
    len = 0;
    if(!st[r]) {
      puts("-1");
      return ;
    }
    For(i,2,sqrt(r)) {
      if(st[i]) continue;
      if(r % i == 0) {
        cout << i << ' ' << r - i << '\n';
        return ;
      } 
    }
    return ;
  }
  if(r / 2 == 1) {
    puts("-1"); return ;
  }
  cout << r / 2 << ' ' << r / 2 << '\n'; 
}

signed main() {
  read(T);
  init();
  while(T--) {
    solve();
  }
  return 0;
}

D. Plus Minus Permutation

Problem

给定三个整数 \(n,x,y\),有排列 \(p_1,p_2,\dots,p_n\),规定这种排列的分数为:

\[(p_{1 \cdot x} + p_{2 \cdot x} + \ldots + p_{\lfloor \frac{n}{x} \rfloor \cdot x}) - (p_{1 \cdot y} + p_{2 \cdot y} + \ldots + p_{\lfloor \frac{n}{y} \rfloor \cdot y}) \]

求所有长度为 \(n\) 的排列的最大分数。

Solve

不是一眼能懂得题了。

分析一下:要想分数最大,\((p_{1 \cdot x} + p_{2 \cdot x} + \ldots + p_{\lfloor \frac{n}{x} \rfloor \cdot x})\) 就要最大,\((p_{1 \cdot y} + p_{2 \cdot y} + \ldots + p_{\lfloor \frac{n}{y} \rfloor \cdot y})\) 就要最小。排列的顺序可以自定。那么就贪心的将 \(n\sim n-\lfloor \frac{n}{x} \rfloor \cdot x+1\) 分配给 \(p_{1 \cdot x}, p_{2 \cdot x}, \ldots, p_{\lfloor \frac{n}{x} \rfloor \cdot x}\),将 \(1\sim \lfloor \frac{n}{y} \rfloor \cdot y\),贪心的分配给 \(p_{1 \cdot y}, p_{2 \cdot y}, \ldots, p_{\lfloor \frac{n}{y} \rfloor \cdot y}\)

细节问题就是:那些 \(\frac{x·y}{\gcd(x,y)}\) 的位置贡献相当于 \(0\)(加减抵消)。所以这些位置要腾出来的话,可以多分配一些给有贡献的位置。

最后答案为:

\[((2n-(\frac{n}{x}-\frac{n}{\frac{xy}{\gcd(x,y)}})+1)·(\frac{n}{x}-\frac{n}{\frac{2xy}{\gcd(x,y)}})) - ((1+(\frac{n}{y}-\frac{n}{\frac{xy}{\gcd(x,y)}}))·(\frac{n}{y}-\frac{n}{\frac{2xy}{\gcd(x,y)}})) \]

看着有点吓人,其实写起来也有亿点点吓人

Code

#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 1e9

using namespace std;

namespace Read {
  template <typename T>
  inline void read(T &x) {
    x=0;T 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();}
    x*=f;
  }
  template <typename T, typename... Args>
  inline void read(T &t, Args&... args) {
    read(t), read(args...);
  }
}

using namespace Read;

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 = 1e7 + 10;

int T, n, x, y;

void solve() {
  read(n, x, y);
  int l1 = n / x, l2 = n / y, l3 = n / (x * y / __gcd(x, y));
  l1 -= l3, l2 -= l3;
  cout << ((2 * n - l1 + 1) * l1 / 2) - ((1 + l2) * l2 / 2) << '\n';
}

signed main() {
  read(T);
  while(T--) {
    solve();
  }
  return 0;
}

E. Data Structures Fan

Problem

给定一个长度为 \(n\) 的数组和一个长度为 \(n\) 的二进制串 \(s\),现有两个操作:

  1. 1 l r,表示将 \(l \le i \le r\) 的所有 \(s_i\) 取反(\(0\)\(1\)\(1\)\(0\));
  2. 2 g\((g\in {0,1})\),表示将所有 \(s_i=g\)\(a_i\) 求异或和;
    \(1\le n\le 10^5,1\le t\le 10^4\).

Solve

脑瘫题,不知道为啥这道题要放在 E。

首先,我们可以计算出操作二中一个串为 \(g=0\)\(g=1\) 的初始异或和。分别记为 \(g_0,g_1\)

对于操作一,无非是将 \(a_i\)\(g_0\)( 或 \(g_1\))里删除,将其加入 \(g_1\)(或 \(g_0\))中,这个操作其实就是将 \(g_0,g_1\) 同时异或 \(l\sim r\) 的异或和。原理是异或的逆运算还是异或。

维护一个前缀异或和即可。但是我打的是线段树(因为一时脑抽没有想到前缀和

时间复杂度 \(O(Tq\log n)\),前缀和可以做到 \(O(Tq)\)

Code

#include <bits/stdc++.h>
#define ll 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 ls p<<1
#define rs p<<1|1

using namespace std;

namespace Read {
  template <typename T>
  inline void read(T &x) {
    x=0;T 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();}
    x*=f;
  }
  template <typename T, typename... Args>
  inline void read(T &t, Args&... args) {
    read(t), read(args...);
  }
}

using namespace Read;

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 = 1e5 + 10;

struct Node {
  int l, r, val;
} t[N << 2];

int n, T, q, a[N], p0, p1;

string s;

void pushup(int p) {
  t[p].val = t[ls].val ^ t[rs].val;
}

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

int ask(int p, int l, int r) {
  if(l <= t[p].l && t[p].r <= r) {
    return t[p].val;
  }
  int mid = (t[p].l + t[p].r) >> 1, ans = 0;
  if(l <= mid) ans ^= ask(ls, l, r);
  if(r > mid) ans ^= ask(rs, l, r);
  return ans;
}

void solve() {
  p0 = p1 = 0;
  read(n);
  For(i,1,n) read(a[i]);
  cin >> s;
  s = " " + s;
  For(i,1,n) {
    if(s[i] == '0') p0 ^= a[i];
    else p1 ^= a[i];
  }
  build(1, 1, n);
  read(q);
  while(q--) {
    int op; read(op);
    if(op == 1) {
      int l, r, p;
      read(l, r);
      p = ask(1, l, r);
      p0 ^= p, p1 ^= p; 
    } else {
      int g; read(g);
      if(g == 0) cout << p0 << ' ';
      else cout << p1 << ' ';
    }
  }
  cout << '\n';
}

signed main() {
  read(T);
  while(T--) {
    solve();
  }
  return 0;
}

F. Selling a Menagerie

Problem

动物园里有 \(n\) 个动物,第 \(i\) 个动物害怕第 \(a_i\) 个动物,第 \(i\) 个动物价值 \(c_i\) 元。现在我要将这些动物全部卖掉。显然,卖掉的动物编号可以构成一个排列 \(p\)

考虑卖掉这些动物时:

  1. \(a_i\)\(i\) 还没有卖掉之前就被卖掉了,现在卖掉 \(i\),可以获得 \(c_i\) 元;
  2. \(a_i\)\(i\) 还没有卖掉之前没被卖掉,现在卖掉 \(i\),可以获得 \(2·c_i\) 元;

求最多能赚多少钱。

Solve

首先,对于动物 \(i\) 来说,肯定希望它害怕动物 \(a_i\) 没有被卖掉。这样可以卖一个好价钱。

反过来想:如果一个动物没有任何其他的动物怕它,那么现在卖掉这个动物之后所得到的贡献肯定不劣。因为它的存在不会影响到其他动物卖出所得的贡献。

如果把“害怕”看成一种连边关系,那么问题就很好解决了。

连边之后,会出现没有构成环的点,和构成环的点。没有构成环的点的卖出顺序随意,上文已经解释过了(没有构成环的点的存在不会影响到其他动物卖出所得的贡献)。不过要注意的是,每一条链上的点或导出子树上的点要按顺序选,不过其都会被选中。

而构成环的点的卖出顺序至关重要,换句话说,起点和终点的所在位置非常关键。起点肯定是要选择 \(c_i\) 最小的点,让损失尽可能的小。然后按顺序选择,卖出即可。

由于每一个点只会被遍历一次,时间复杂度 \(O(Tn)\)

Code

#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 1e10

using namespace std;

namespace Read {
  template <typename T>
  inline void read(T &x) {
    x=0;T 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();}
    x*=f;
  }
  template <typename T, typename... Args>
  inline void read(T &t, Args&... args) {
    read(t), read(args...);
  }
}

using namespace Read;

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 = 1e5 + 10; 

int T, n, a[N], d[N], c[N], ans, Ans[N], len;

bool vis[N];

void solve() {
  memset(vis, 0, sizeof vis); 
  ans = len = 0;
  read(n);
  For(i,1,n) read(a[i]), d[a[i]]++;
  For(i,1,n) read(c[i]);
  bool f = 1;
  while(f) {
    f = 0;
    For(i,1,n) {
      if(!d[i] && !vis[i]) Ans[++len] = i, d[a[i]]--, vis[i] = 1, f = 1;
    }
  }
  For(i,1,n) {
    if(d[i]) {
      int x = i, mn = inf, en, st;
      while(!vis[x]) {
        vis[x] = 1;
        if(mn > c[x]) {
          mn = c[x];
          st = a[x];
          en = x;
        }
        d[a[x]]--;
        x = a[x];
      }
      while(st != en) {
        Ans[++len] = st;
        st = a[st];
      }
      Ans[++len] = en;
    }
  }
  For(i,1,len) cout << Ans[i] << ' ';
  cout << '\n';
}

signed main() {
  read(T);
  For(i,1,T) {
    solve();
  }
  return 0;
}

G. Replace With Product

Problem

给定一个长度为 \(n\) 的数组 \(a\),现在可以进行一次操作:

  • 选择两个正整数 \(l,r(l\le r)\),将 \(a[l\dots r]\) 的所有数删除,并替换为 \(\prod\limits_{i=l}^ra_i\)
    最后的总贡献为所有数的加和,请选择 \(l,r\),最大化总贡献。

Solve

小 trick 题。

首先,前缀和后缀 \(1\) 选入操作区间显然是不会优的,因为他对于乘法是没有贡献的,而对于加法有 \(+1\) 的贡献。

发现一个性质:如果一些数的乘积很大,达到了某一个上限时,这些数的和一定比这些数的乘积要小。这个上限具体为多少我们并不知道,但是,只要它是一个很大的数字,那么上述事件发生的概率就会很大。考虑到 \(1 \le a_i \le 10^9\),我们就将上限设为 \(10^{18}\),这是一个接近于 \(2^{60}\) 的数字。这也就意味着,当所有数的乘积小于 \(10^{18}\) 时,所有大于 \(2\) 的数字不会超过 \(60\) 个。

对于所有乘积大于上限的数,直接摒弃前缀和后缀 \(1\) 之后的区间即为答案,因为要尽可能的让乘积变大。

显然,操作区间的左右端点不会出现在数字 \(1\) 上,因为出现在了数字 \(1\) 上肯定会比出现在大于 \(1\) 的数字上更劣。所以只要暴力枚举所有大于 \(1\) 的数的位置,然后利用前缀和与前缀积快速统计贡献,最后取最大贡献所对应的区间即可。

时间复杂度 \(O(TnK^2)\),其中 \(K\le 60\)

Code

#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 1e18

using namespace std;

namespace Read {
  template <typename T>
  inline void read(T &x) {
    x=0;T 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();}
    x*=f;
  }
  template <typename T, typename... Args>
  inline void read(T &t, Args&... args) {
    read(t), read(args...);
  }
}

using namespace Read;

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 T, n, a[N], sum[N], ans, ansl, ansr, len, no1[N], pre[N];

void solve() {
  read(n);
  For(i,1,n) read(a[i]), sum[i] = sum[i-1] + a[i];
  int l, r, k; __int128 p = 1; len = 0, ans = 0, ansl = 1, ansr = 1;
  For(i,1,n) {
    p *= a[i];
    if(p > inf) break;
  }
  if(p > inf) {
    For(i,1,n) {
      l = i;
      if(a[i] != 1) {
        break;
      }
    }
    FOR(i,n,1) {
      r = i;
      if(a[i] != 1) {
        break;
      }
    }
    cout << l << ' ' << r << '\n'; 
    return ;
  }
  pre[0] = 1; 
  For(i,1,n) pre[i] = pre[i-1] * a[i];
  For(i,1,n) {
    if(a[i] != 1) no1[++len] = i;
  }
  For(i,1,len) {
    For(j,1,len) {
      if(no1[i] > no1[j]) continue;
      if(ans < sum[n] - (sum[no1[j]] - sum[no1[i]-1]) + (pre[no1[j]] / pre[no1[i]-1])) {
        ans = sum[n] - (sum[no1[j]] - sum[no1[i]-1]) + (pre[no1[j]] / pre[no1[i]-1]);
        ansl = no1[i], ansr = no1[j];
      }
    }
  }
  cout << ansl << ' ' << ansr << '\n';
}

signed main() {
  read(T);
  while(T--) { 
    solve();
  }
  return 0;
}
posted @ 2023-09-09 17:05  Daniel_yzy  阅读(17)  评论(0编辑  收藏  举报