2023牛客寒假算法基础集训营第一场题解(A-M)

比赛链接:[2023牛客寒假算法基础集训营1]

比赛情况: 10/1310/1310/13

本场个人觉得难度排行: $$A≤C≤L≤H≤K≤D≤M≤G≤F≤E≤B≤I≤JA\le C\le L\le H\le K\le D\le M\le G\le F\le E\le B\le I\le JA\le C\le L\le H\le K\le D\le M\le G\le F\le E\le B\le I\le J$$

以下是赛时通过的代码,只A了10题,最后1h直接开摆了,还是太菜了~~

值得注意的就是千万千万不要相信题目名字!!!!!!

B,I,J 后面再补充。。。。。

这里是牛客出题人给出的难度分析:

下面我们按照题目难度顺序来讲:


A题[World Final? World Cup! (I)]

算法考察:模拟

题意:给定一个长度为10的01串,1代表获胜,0代表失败,有两个人PK,输出在第几回合决出胜负。

解:

我们直接定义a1,b1分别为在当前轮数最少获胜的次数,a2,b2为该人还可能获胜的次数,这里我们直接对每轮进行判断 (a1+a2-b1)*(b1+b2-a1)<0(a1+a2-b1)*(b1+b2-a1)<0 是否成立即可。

AC代码:

void solve() {
  char s[20];
  cin >> s + 1;
  int a1 = 0, a2 = 5, b1 = 0, b2 = 5;
  rep(i, 1, 10) {
    if (i & 01) {
      if (s[i] == '1')a1++;
      a2--;
    }
    else {
      if (s[i] == '1')b1++;
      b2--;
    }
    if ((a1 + a2 - b1) * (b1 + b2 - a1) < 0) {
      cout << i << endl; return;
    }
  }
  cout << -1 << endl;
}

C题 [现在是,学术时间 (I)]

算法考察:思维

题意:分配论文发表,使得 \sum_{i=1}{n}{hi}\sum_{i=1} 最大,初始每个人论文发表数为0,如果当前论文的引用量大于发表的文章数量,则获得一点贡献,输出最大贡献。(一个人至多发表一篇)

解:

我们发现这是一个诈骗题,一开始所有人论文发表都是0,那么只要引用量大于0,贡献就会加1,所有我们直接按照原来的顺序即可。

AC代码:

void solve() {
  int n;
  cin >> n;
  vector<int>a(n + 10);
  rep(i, 1, n)cin >> a[i];
  int res = 0;
  rep(i, 1, n)if (a[i] != 0)res++;
  cout << res << endl;
}

L题 [本题主要考察了运气]

算法考察:高中数学期望

题意:有20人,分别属于5个团体,每个团体4人,输出与答案最接近的编号,具体看题目。

解:

每个团呵每个人彼此没有区别,所以依次猜就可以了。

猜团: 5个团,第1次猜中概率是0.2,第二次是0.2,第三次是0.2,第四次就是0.4

猜人:4人,第一次0.25,第二次0.25,第三次0.5

数学期望 E=1*0.2+2*0.2+3*0.2+4*0.4+1*0.25+2*0.25+3*0.5=5.05E=1*0.2+2*0.2+3*0.2+4*0.4+1*0.25+2*0.25+3*0.5=5.05

所以 ans->3.45+0.05*i=5.05ans->3.45+0.05*i=5.05 -> ans=32ans=32

AC代码:(PHP)

32

H题 [本题主要考察了DFS]

算法考察:暴力,模拟

题意:给你 n*n-1n*n-1 块拼图,制作成本=10-削去几个半圆+补上几个半圆,输出制作成本,其中每块拼图用0,1,2表示不变、凸出、缺口。

解:

统计几个削去的和几个补上的,直接cout就可以了。

AC代码:

void solve()
{
  int n;
  string s[410];
  cin >> n;
  int L = 0, R = 0;
  rep(i, 1, n * n - 1) {
    cin >> s[i];
    rep(j, 0, 3) {
      if (s[i][j] == '1')
        L++;
      if (s[i][j] == '2')
        R++;
    }
  }
  ll ans = 10 + L - R;
  cout << ans << endl;
}

K题 [本题主要考察了dp]

算法考察:状压DP || 贪心+思维+暴力

题意:给定n,m,代表长度为n的01串,有m个1,对长度为3的连续子区间,cout(1)>cout(0)区间数量的个数,输出构造出来的01串坏区间最少的字符串有几个坏区间。

解法一:

贪心+思维+暴力

我们直接构造1001001001111,前面全部以100的形式存在,0不足了再补1,最后暴力求出几个坏区间即可。

AC代码:

void solve()
{
  int n, m;
  cin >> n >> m;
  int cn1 = m, cn0 = n - m;
  string s;
  rep(i, 1, n) {
    if (i % 3 == 1) {
      if (cn1 > 0)
        cn1--, s += '1';
      else
        cn0--, s += '0';

    }
    else {
      if (cn0 > 0)
        s += '0', cn0--;
      else
        cn1--, s += '1';
    }
  }
  ll ans = 0;
  for (int i = 0; i + 2 < n; ++i) {
    int res = 0;
    rep(j, i, i + 2) {
      if (s[j] == '1')res++;
      else res--;
    }
    if (res > 0)ans++;
  }
  cout << ans << endl;
}

解法二:

利用状压DP来写,创建 dp[1050][1050][10]dp[1050][1050][10] , dp[i][j][[k]dp[i][j][[k] 表示前i个数字,有j个1,以k的二进制结尾的坏区间最少的个数。

状态转移方程: dp[i][j][k]=min(dp[i-1][j-(k\&1)][(k>>1)],dp[i-1][j-(k\&01)][k>>1|4])+baddp[i][j][k]=min(dp[i-1][j-(k\&1)][(k>>1)],dp[i-1][j-(k\&01)][k>>1|4])+bad

状态转移方程含义:

当前状态由选取前一个数字 (i-1)(i-1) , j j 前一个状态1的数量要通过当前状态 kk 二进制结尾是否有1,即

j-(k\&01)j-(k\&01) ,其中 j-(k\&01)>=0j-(k\&01)>=0 , kk 之前的一个状态有两种情况,可能是 0k0k 也可能 1k1k ,枚举这两种情况取 \min\min 即可。

状态转移方程初始化:

对于 dpdp 数组全部置为 infinf ,对于 dp[3][cnt(k)][k]=cnt(k)>=2dp[3][cnt(k)][k]=cnt(k)>=2 ,其中 cnt(k)cnt(k) 为k二进制中1的数量。

AC代码:

void solve() {
  int n, m; cin >> n >> m;
  vector dp(n + 10, vector(m + 10, vector<int>(10, inf)));
  //选取前i个数字,j个为1,以k的二进制结尾,坏区间的个数
  rep(i, 0, 7) {
    int x = (i & 01) + (i >> 1 & 01) + (i >> 2 & 01);
    dp[3][x][i] = x >= 2;
  }
  int ans = inf;
  rep(i, 4, n) {
    rep(j, 0, m) {
      rep(k, 0, 7) {
        int te = (k & 01) + (k >> 1 & 01) + (k >> 2 & 01);//转移后的1
        int bad = te >= 2;
        if (j - (k & 1) >= 0)//符合转移条件
          dp[i][j][k] = min(dp[i - 1][j - (k & 01)][k >> 1],
            dp[i - 1][j - (k & 01)][(k >> 1) | 4]) + bad;
      }
    }
  }
  rep(i, 0, 7)
    ans = min(ans, dp[n][m][i]);
  cout << ans << endl;
}

D题 [现在是,学术时间 (II)]

算法考察:分类讨论

题意:给你一个点A (x,y)(x,y) ,它和原点构成一个矩形的GT区域,我们定义两个矩形的IOU == 两个矩形的交集 // 两个矩形的并集,再给你点P (x_{p},y_{p})(x_{p},y_{p}) ,以P为另外一个矩形的顶点,构建一个边都平行于坐标轴的预测目标框,使得与GT目标的IOU最大,输出这个最大值,误差不超过 10{-4}10

解:

如图所示(盗了炸鸡giegie的图),我们当前的GT区域为区域1,我们可以选择的有 2,3,42,3,4 ,

对每种情况进行讨论即可。

PS:这题我的代码就不放了,分类讨论写的太丑了,然后去看别人的代码的时候发现了一个非常简便的代码。

AC代码:

void solve() {
  int x, y, xp, yp, s, a, b;
  cin >> x >> y >> xp >> yp;
  a = max(abs(xp - x), xp);
  b = max(abs(yp - y), yp);
  s = a * b + x * y;
  a = min(a, x);
  b = min(b, y);
  double ans;
  ans = double(a * b) / (s - a * b);
  printf("%.9f\n", ans);
}

M题 [本题主要考察了找规律]

算法考察:DP+背包思想

题意:有n个人,m个仙贝,需要分发给n个人,同一个人不能送两次仙贝,允许自己剩下来仙贝,允许有人一个也不送,每次送仙贝都会增加好感值,设 当前剩下的仙贝 xx,并送个那个人 yy 个仙贝,好感值 == y/xy/x (double)

输出所有人最大好感值之和是多少。

解:

因为 n,mn,m 都在 500500 以内,所以我们直接暴力DP就可以了。

DPDP 含义: dp[i][j]表示前i个人送出去了j个仙贝的好感度之和dp[i][j]表示前i个人送出去了j个仙贝的好感度之和

状态转移方程: dp[i][j]=\max(\{dp[i][j],dp[i-1][j],dp[i-1][k]+te\})dp[i][j]=\max(\{dp[i][j],dp[i-1][j],dp[i-1][k]+te\})

AC代码:

double dp[1000][1000];//到第i个一共拿了j个
void solve() {
  ll m, n;
  cin >> n >> m;
  int ans = 0;
  rep(i, 1, n) {
    rep(j, 0, m) {
      rep(k, 0, j) {
        double te = 1.0 * (j - k) / (m - k);
        if (m == k || j == k)dp[i][j] = max(dp[i - 1][j], dp[i][j]);
        dp[i][j] = max(dp[i][j], dp[i - 1][k] + te);
      }
    }
  }
  cout << SQR(9) << dp[n][m] << endl;
}

G题 [鸡格线]

算法考察:多种数据结构||set维护存所有未到达 0的下标||并查集维护某个数下一个未到达 0的下标

赛时的话我直接用线段树来维护了

题意:有一个长为 nn 的数组 aa ,你需要支持以下两种操作:

1、输入 l,r,k,l,r,k, ,对区间 [l,r][l,r] 中所有数字执行 a_{i}=f(a_{i})a_{i}=f(a_{i}) 操作 kk 次(式中等号表示赋值操作),之中 f(x)=round(10\sqrt x)f(x)=round(10\sqrt x) , roundround 为四舍五入函数。

2、输出当前数组所有数字的和。

你需要正确处理 mm 次这样的操作。 1\le n,m\le10^5,0\le a_{i}\le10^91\le n,m\le10^5,0\le a_{i}\le10^9

解:

性质: f(x_{0})f(x_{0}) 经过不多次数的操作会收敛到一个不变的值 f(x_{0})=x_{0}f(x_{0})=x_{0}

其中 x_{0}x_{0} 有三个: 0,99,1000,99,100

我们建立树的节点并且直接重载 pushuppushup 操作:

const int MAXN = 4e5 + 5;
struct node
{
  int val, flag;
  node() {}
  node(int x, int y) :val(x), flag(y) {}
  node operator + (const node& M) const {
    node p;
    p.val = val + M.val;
    p.flag = flag + M.flag;
    return p;
  }
  //默认复制构造
  node(const node& M) {
    val = M.val;
    flag = M.flag;
  }
  void operator=(const node& M) {
    val = M.val;
    flag = M.flag;
  }  
}tr[MAXN];

这里我们对每次操作进行判断,然后记录下操作后是否等于原来的值,如果等于flag=1,否则flag=0;

这里是一个优化,我们可以直接在修改操作的时候,在满足这个条件直接return

if (tr[u].flag == r - l + 1)return;//优化核心

下面直接给出AC代码,就正常的线段树直接维护就可以AC了

const int MAXN = 4e5 + 5;
struct node
{
  int val, flag;
  node() {}
  node(int x, int y) :val(x), flag(y) {}
  node operator + (const node& M) const {
    node p;
    p.val = val + M.val;
    p.flag = flag + M.flag;
    return p;
  }
  //默认复制构造
  node(const node& M) {
    val = M.val;
    flag = M.flag;
  }
  void operator=(const node& M) {
    val = M.val;
    flag = M.flag;
  }
}tr[MAXN];

int a[MAXN];
int ch(int x) {
  long double p = 10.0 * sqrt(x);
  int k = (p + 0.5);
  if (k == x)return 1;
  return 0;
}
int cacl(int x)
{
  double pp = 10.0 * sqrt(x);
  int ans = (pp + 0.5);
  return ans;
}
void build(int u, int l, int r)
{
  if (l == r) {
    tr[u] = {a[l],ch(a[l]) };
    return;
  }
  int mid = (l + r) >> 1;
  build(u << 1, l, mid);
  build(u << 1 | 1, mid + 1, r);
  tr[u] = tr[u << 1] + tr[u << 1 | 1];
}

void modify(int u, int l, int r, int L, int R)
{
  if (tr[u].flag == r - l + 1)return;//优化核心
  if (l == r)
  {
    a[l] = cacl(a[l]);
    tr[u] = { a[l],ch(a[l]) };
    return;
  }
  int mid = (l + r) >> 1;
  if (R <= mid)
    modify(u << 1, l, mid, L, R);
  else if (L > mid)
    modify(u << 1 | 1, mid + 1, r, L, R);
  else
  {
    modify(u << 1, l, mid, L, mid);
    modify(u << 1 | 1, mid + 1, r, mid + 1, R);
  }
  tr[u] = tr[u << 1] + tr[u << 1 | 1];
}

void solve() {
  int n, m;
  cin >> n >> m;
  rep(i, 1, n)cin >> a[i];
  build(1, 1, n);
  while (m--)
  {
    int op; cin >> op;
    if (op == 1){
      int l, r, k;
      cin >> l >> r >> k;
      k = min(k, 16LL);
      rep(i, 1, k)
        modify(1, 1, n, l, r);
    }
    else cout << tr[1].val << endl;
  }
}

F题 [鸡玩炸蛋人]

算法考察:并查集+思维

题意:有一个鸡要下蛋,鸡在一个n个节点,m条边的无向图中,可以通过相连的边移动到没有蛋的节点上,每到一个位置可以下任意数量的蛋,给出最终的图上下蛋情况,问鸡从S->T的方案数。若无合法方案输出0。

解:

诈骗题,一个位置可以下许多蛋,那就说明只要考虑最终情况有蛋的点有哪些,不需要考虑有几个蛋,而且所有的蛋必须处于同一连通块,因为鸡不能跨越一个连通块到另外一个去。所以对于不合法的情况就是蛋不下在同一个连通块,对于没有下蛋的,我们直接对于每个连通块大小平方相加就可以了。

AC代码:

int a[N], p[N];
int find(ll x) {
  return p[x] == x ? x : p[x] = find(p[x]);
}
void solve() {
  int n, m;
  cin >> n >> m;
  rep(i, 0, n + 10)p[i] = i;
  set<int>vis;
  map<int, int>mp;
  bool ok = 0;
  rep(i, 1, m) {
    int u, v;
    cin >> u >> v;
    u = find(u);
    v = find(v);
    if (u != v)
      p[u] = v;
  }
  rep(i, 1, n)cin >> a[i];
  rep(i, 1, n)mp[find(i)]++;
  int mx = *max_element(a + 1, a + 1 + n);
  int ans = 0;
  rep(i, 1, n) {//all 的炸弹在一个连通块
    if (a[i]) {
      if (vis.empty())
        vis.insert(find(i));
      else if (vis.find(find(i)) == vis.end()) {
        ok = 1;
        break;
      }
    }
  }
  if (ok) {
    cout << 0 << endl;
    return;
  }
  if (mx == 0) {
    for (auto& i : mp)
      ans += i.second * i.second;
    cout << ans << endl; return;
  }
  else {
    ans = mp[*vis.begin()] * mp[*vis.begin()];
    cout << ans << endl;
    return;
  }
}

E题 [鸡算几何]

算法考察:几何+叉乘

题意:给定 A,B,C,D,E,FA,B,C,D,E,F ,六个点,其中 A,B,CA,B,C 组成一个L型,通过三种操作使得 A,B,CA,B,C 构成的L型能和 D,E,FD,E,F 对应起来,判断是否进行了第三种操作。

解:

判断有无第三种操作只需要判断铁丝有没有变化,铁丝不会发生形变,题目不保证,ABC和DEF对应,考虑叉积判断。第三个操作的特点比如A变为关于BC的对称点,另外我们还要特判长度是否相等的情况,这里我们要用到fabs和eps用于判断。

AC代码:

struct point { double x, y; };
point a, b, c, d, e, f;
double DBA, DBC, DED, DEF;
double Dist(point u, point v)
{
  double dx = u.x - v.x, dy = u.y - v.y;
  double ans = sqrt(dx * dx + dy * dy);
  return ans;
}
void init()
{
  DBA = Dist(b, a);
  DBC = Dist(b, c);
  DED = Dist(e, d);
  DEF = Dist(e, f);
}
int ch(point u, point v, point w)
{
  point uv, uw;
  uw.x = w.x - u.x;
  uw.y = w.y - u.y;
  uv.x = v.x - u.x;
  uv.y = v.y - u.y;
  int f = uv.x * uw.y - uv.y * uw.x;
  if (f > 0)return 1;
  if (f == 0)return 0;
  return -1;
}
void solve() {
  cin >> a.x >> a.y >>
    b.x >> b.y >>
    c.x >> c.y >>
    d.x >> d.y >>
    e.x >> e.y >>
    f.x >> f.y;
  init();
  if (fabs(DBA - DBC) < 1e-9) {
    No();
    return;
  }
  if (fabs(DBA - DED) < 1e-9) {
    if (ch(b, a, c) == ch(e, d, f))No();
    else Yes();
    return;
  }
  if ((ch(b, a, c) == ch(e, f, d)))No();
  else Yes();
}

发布于 2023-01-17 15:32・IP 属地安徽

posted @ 2023-09-28 12:09  Nondifferent  阅读(17)  评论(0编辑  收藏  举报