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

前言#

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

然后就是时间把握不好,CCF 和模拟赛基本都是 3h4h,而 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#

水题。

总水量保持不变,所以两个杯子在操作完之后的单位水量为 a+b2,并且 |aa+b2|=|ba+b2|。所以最后的答案为 |aa+b2|c,向上取整是因为单次舀 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 个陷阱,每一个陷阱都有一个所在位置 di 和激活时间 si,当你进入有陷阱的房间时,会激活陷阱,陷阱将在 si 秒后触发。陷阱触发后,你不能再次进入此房间或者从此房间出去。

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

Solve#

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

首先必须有去有回。当一个陷阱被出发后,你必须在某个点折返回来,并且要保证不能受陷阱影响。对于所有的陷阱,都是如此。每个陷阱都会有一个最大的,能到达并且能返回的点,将其记为 gi,答案为 min1ingi

考虑如何计算 gi,我们将去的路和回的路拉成一条直线,这条直线的中点即为 gi=(di+(si1))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,使:

  • la+br
  • gcd(a,b)1

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

Solve#

构造题。

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

  • r 为偶数, a=b=r2
  • r 为奇数,l<ra=b=r12

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

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

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

时间复杂度 O(Tn).

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,有排列 p1,p2,,pn,规定这种排列的分数为:

(p1x+p2x++pnxx)(p1y+p2y++pnyy)

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

Solve#

不是一眼能懂得题了。

分析一下:要想分数最大,(p1x+p2x++pnxx) 就要最大,(p1y+p2y++pnyy) 就要最小。排列的顺序可以自定。那么就贪心的将 nnnxx+1 分配给 p1x,p2x,,pnxx,将 1nyy,贪心的分配给 p1y,p2y,,pnyy

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

最后答案为:

((2n(nxnxygcd(x,y))+1)·(nxn2xygcd(x,y)))((1+(nynxygcd(x,y)))·(nyn2xygcd(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,表示将 lir 的所有 si 取反(0110);
  2. 2 g(g0,1),表示将所有 si=gai 求异或和;
    1n105,1t104.

Solve#

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

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

对于操作一,无非是将 aig0( 或 g1)里删除,将其加入 g1(或 g0)中,这个操作其实就是将 g0,g1 同时异或 lr 的异或和。原理是异或的逆运算还是异或。

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

时间复杂度 O(Tqlogn),前缀和可以做到 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 个动物害怕第 ai 个动物,第 i 个动物价值 ci 元。现在我要将这些动物全部卖掉。显然,卖掉的动物编号可以构成一个排列 p

考虑卖掉这些动物时:

  1. aii 还没有卖掉之前就被卖掉了,现在卖掉 i,可以获得 ci 元;
  2. aii 还没有卖掉之前没被卖掉,现在卖掉 i,可以获得 2·ci 元;

求最多能赚多少钱。

Solve#

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

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

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

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

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

由于每一个点只会被遍历一次,时间复杂度 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(lr),将 a[lr] 的所有数删除,并替换为 i=lrai
    最后的总贡献为所有数的加和,请选择 l,r,最大化总贡献。

Solve#

小 trick 题。

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

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

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

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

时间复杂度 O(TnK2),其中 K60

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

作者:Daniel-yao

出处:https://www.cnblogs.com/Daniel-yao/p/17689788.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   Daniel_yzy  阅读(19)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示