2024牛客暑期多校训练营3

写在前面

比赛地址:https://ac.nowcoder.com/acm/contest/81598

以下按个人难度向排序。

dztlb 大神和妹子约会去了,于是继续单刷。

妈的今天签到都是什么玩意儿妈的 A 做红温了想到自己一个人直接不做了跑去写 JD。手感还不错调出来了没太烂妈的

另外今天英文题面写的很烂呃呃,被 D 题面什么意思硬控二十分钟才看懂上个厕所就会了妈的。

L

签到。

一眼想到全改成雷,但是不让。

地球人都知道数独的 9 个 \(9\times 9\) 的中格子里都是 1~9 的一个排列。构造任意,于是仅需随便找一个不在角上的 8 留着,其余位置均改成雷即可。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
std::string s[10];
bool flag, tag[10][10];
//=============================================================
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  for (int i = 1; i <= 9; ++ i) {
    std::cin >> s[i];
    s[i] = "$" + s[i];
  }
  for (int i = 2; i <= 8; ++ i) {
    for (int j = 2; j <= 8; ++ j) {
      if (s[i][j] == '8') tag[i][j] = flag = 1; 
      if (flag) break;
    }
    if (flag) break;
  }
  for (int i = 1; i <= 9; ++ i) {
    for (int j = 1; j <= 9; ++ j) {
      if (tag[i][j]) std::cout << s[i][j];
      else std::cout << '*';
    }
    std::cout << "\n";
  }
  return 0;
}

B

数学。

手玩下发现仅使用一个推进器可以达到的距离仅有 \(D\bmod h\)\(h - D\bmod h\)

再观察样例加大力手玩发现答案似乎与给定数列的倍数关系有关,当 \(\gcd = 1\) 时答案基本上都是 0。

可以证明,对于若干推进器 \(h_1, h_2, \cdots, h_n\),等价于一个推进器 \(d = \gcd(h_1, h_2, \cdots, h_n)\),证明懒得写了详见官方题解。则答案即为 \(\min(D\bmod d, d - D\bmod d)\)

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 110;
//=============================================================
int n;
LL D, h[kN];
//=============================================================
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  std::cin >> n >> D;
  
  LL d = 0;
  for (int i = 1; i <= n; ++ i) {
    std::cin >> h[i];
    d = std::__gcd(d, h[i]);
  }
  std::cout << std::min(D % d, d - D % d) << "\n";
  return 0;
}

A

数学,贪心,模拟。

场上直接扔了呃呃我是脑瘫,加一个每个人跑的轮数的上界就过了妈的

一个显然的贪心是,每次把船划到对面后,选体力最高的若干人划回来。显然把体力更小的人搞回来让他再多干活儿一定不会更优。

记船划过去又划回来的这个过程为“一轮”,发现在最优情况下,每轮能将 \(R-L\) 个人运到对面并不需要回来,最后一轮一定运 \(R\) 个人,则进行的轮数一定是 \(k=\left\lceil \frac{n - R}{R - L}\right\rceil\)

显然每个人参与划船的轮数一定不大于 \(k\),则每个人可以参与划船的轮数为 \(b_i = \min(\left\lfloor\frac{a_i}{2}\right\rfloor, k)\)。发现通过上面这个贪心一定可以让每个人都取到最大划船轮数,则仅需判断所有人的划船轮数之和除 \(L\) 得到的最大划船轮数 \(\frac{\sum{b_i}}{L}\) 是否不小于 \(k\) 即可。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 5e5 + 10;
//=============================================================
int n, L, R, h[kN];
//=============================================================
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  std::cin >> n >> L >> R;
  LL count = 0, minround = ceil(1.0 * (n - R) / (R - L));
  for (int i = 1; i <= n; ++ i) {
    std::cin >> h[i];
    count += std::min((h[i] - 1) / 2ll, minround);
  }
  if (count >= minround * L) std::cout << "Yes\n";
  else std::cout << "No\n";
  return 0;
}
/*
9 1 8
2 2 2 2 2 2 2 2 2

5 3 4
3 3 3 3 3

6 3 4
3 3 3 3 3 3

7 3 4
3 3 3 3 3 3 3

7 3 4
3 3 3 3 3 3 5
*/

J

基环树,二分,倍增。

先把字符串 \(T\) 复制到长度不小于 \(2\times a - 1\) 以令其包含所有小局中的情况。

一个显然的想法是对于给定字符串的每一个位置,求得以该位置为小局起点时,使这一小局获胜或失败 \(a\) 次的最近的小局终点,可以很容易在二倍长的字符串上,前缀和维护 0/1 的数量二分实现,较近的终点即对应这一小局的唯一结果,并可以找到下一局的起点。发现每个小局起点都唯一地指向下一小局起点,实际上构成了一棵基环内向树,边有边权表示这一小局的获胜情况。

对于每个起点判定大局的结果,等价于对于这棵基环树上节点 \(1\sim n\),找到以它为起点的最短的路径,该路径代表获胜或失败了 \(b\) 次;然而不太好直接求,于是考虑对于胜利与失败两种情况二分答案,二分枚举路径的长度 \(d\) 并检查长为 \(d\) 的路径上是否有 \(b\) 条边代表胜利/失败,路径长度的限制与求和可以很简单地倍增维护。求得较短的一条路径即为大局的结果。

单次询问 \(O(\log b\log n)\) 级别。预处理基环树并倍增后,对于节点 \(1\sim n\) 都使用上述算法求解即可。总时间复杂度 \(O(n\log a + n\log b\log n)\) 级别。

代码中并没有显式地建树,而是直接在预处理的下一局起点上倍增。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
#define pr std::pair
#define mp std::make_pair
const int kN = 1e6 + 10;
const LL kInf = 1e12 + 2077;
//=============================================================
int n, st, a, b;
LL sum[kN][2], next_havea[kN][2];
std::string s, t;
bool ans[kN];

int fa[kN][21], dis[kN][21], g[kN][2][21];
//=============================================================
LL getsum(int L_, int R_, int ch_) {
  LL sl = 0, sr = (R_ / st) * sum[st - 1][ch_] + sum[R_ % st][ch_];
  if (L_ > 0) sl = ((L_ - 1) / st) * sum[st - 1][ch_] + sum[(L_ - 1) % st][ch_];
  return sr - sl;
}
int getpos(int pos_) {
  return pos_ % st;
}
LL query(int u_, int ch_, int lim_) {
  int u = u_, cnt = 0, d = 0;
  for (int i = 20; i >= 0; -- i) {
    if (d + dis[u][i] <= lim_) {
      cnt += g[u][ch_][i];
      d += dis[u][i];
      u = fa[u][i];
    }
  }
  return cnt;
}
void solve() {
  for (int i = 0; i < st; ++ i) {
    int p = (next_havea[i][1] != kInf);
    fa[i][0] = getpos(next_havea[i][p] + 1);
    g[i][0][0] = (!p), g[i][1][0] = p; 
    dis[i][0] = 1;
  }

  for (int i = 1; i <= 20; ++ i) {
    for (int u = 0; u < st; ++ u) {
      fa[u][i] = fa[fa[u][i - 1]][i - 1];
      g[u][0][i] = g[u][0][i - 1] + g[fa[u][i - 1]][0][i - 1];
      g[u][1][i] = g[u][1][i - 1] + g[fa[u][i - 1]][1][i - 1];
      dis[u][i] = dis[u][i - 1] + dis[fa[u][i - 1]][i - 1];
    }
  }

  for (int i = 0; i < n; ++ i) {
    LL d0 = kInf, d1 = kInf;
    for (int l = b, r = 2 * b - 1; l <= r; ) {
      int mid = (l + r) >> 1;
      if (query(i, 0, mid) >= b) {
        d0 = mid;
        r = mid - 1;
      } else {
        l = mid + 1;
      }
    }
    for (int l = b, r = 2 * b - 1; l <= r; ) {
      int mid = (l + r) >> 1;
      if (query(i, 1, mid) >= b) {
        d1 = mid;
        r = mid - 1;
      } else {
        l = mid + 1;
      }
    }
    if (d1 < d0) ans[i] = 1;
  }
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  std::cin >> n >> a >> b;
  std::cin >> s;

  t = s;
  for (int i = n; i <= (2 * a - 1); i += n) t += s;

  st = t.length();
  for (int i = 0; i < st; ++ i) {
    sum[i][0] = t[i] == '0', sum[i][1] = t[i] == '1';
    if (i > 0) sum[i][0] += sum[i - 1][0], sum[i][1] += sum[i - 1][1];
  }
  for (int i = 0; i < st; ++ i) {
    for (int j = 0; j <= 1; ++ j) {
      next_havea[i][j] = kInf;

      for (int l = i, r = i + 2 * a - 1; l <= r; ) {
        int mid = (l + r) >> 1;
        if (getsum(i, mid, j) >= a) {
          next_havea[i][j] = mid;
          r = mid - 1;
        } else {
          l = mid + 1;
        }
      }
    }

    int p = (next_havea[i][1] < next_havea[i][0]);
    next_havea[i][!p] = kInf;
  }

  // std::cout << "\n\n";
  // std::cout << t << "\n";
  // for (int i = 0; i < st; ++ i) {
  //   std::cout << next_havea[i][0] << " " << next_havea[i][1] << "\n";
  // }
  solve();
  for (int i = 0; i < n; ++ i) std::cout << ans[i];
  return 0;
}

D

构造。

发现当所有牌两端均不同时可以随便构造,直接随便枚举加到链的末端即可。于是主要问题在于把两端相同的牌加到链里;又发现可以先将两种不同的两端相同的牌组合一下,等价于一张两端不同的牌。于是先将两端相同的牌尽可能地两两组合,则最后仅会剩下一种。

于是考虑在构造牌两端不同的链时,每加一张牌就检查能否补一张剩下的一种两端相同的牌即可。考虑两两匹配时,每次选择出现次数最多的两种牌,使得最终剩下的一种牌的权值并不会在构造两端不同的链时出现太多次,可以保证构造方案大概率成功。

不过我不太能证完全正确、、、感觉可能是数据有点水毕竟我这个做法确实不太好保证剩下的两端相同的牌数量最少,不过卡过去了就算我牛逼了。另外孩子们我因为没输出 Yes 吃了两发我是唐中之唐

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
#define pr std::pair
#define mp std::make_pair
const int kN = 2e5 + 10;
//=============================================================
int n, datanum, data[kN << 1];
pr<int, int> a[kN];
int equaledge[kN << 1];
// std::set<int> edge[kN << 1];
std::vector<pr<int, int> > edge;
std::set<pr <int,int> > countedge;
//=============================================================
void init() {
  std::cin >> n;
  for (int i = 1; i <= n; ++ i) {
    pr<int,int> x; std::cin >> x.first >> x.second;
    a[i] = x, data[i] = x.first, data[n + i] = x.second;
  }
  std::sort(data + 1, data + 2 * n + 1);
  datanum = std::unique(data + 1, data + 2 * n + 1) - data - 1;
  for (int i = 1; i <= n; ++ i) {
    a[i].first = std::lower_bound(data + 1, data + datanum + 1, a[i].first) - data;
    a[i].second = std::lower_bound(data + 1, data + datanum + 1, a[i].second) - data;

    if (a[i].first == a[i].second) ++ equaledge[a[i].first];
    // else edge[a[i].first].insert(a[i].second), edge[a[i].second].insert(a[i].first);
    else edge.push_back(a[i]);
  }
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  init();

  for (int i = 1; i <= datanum; ++ i) {
    if (equaledge[i]) countedge.insert(mp(equaledge[i], i));
  }
  while (1) {
    pr<int,int> x, y;
    if (countedge.empty()) break;
    std::set<pr <int,int> >::iterator mincount = (--countedge.end());
    x = *mincount;
    countedge.erase(mincount);
    
    if (countedge.empty()) {
      countedge.insert(x);
      break;
    }
    std::set<pr <int,int> >::iterator maxcount = (--countedge.end());
    y = *maxcount;
    countedge.erase(maxcount);
    
    edge.push_back(mp(-x.second, -y.second));

    if (x.first > 1) countedge.insert(mp(x.first - 1, x.second));
    if (y.first > 1) countedge.insert(mp(y.first - 1, y.second));
  }
  int remain = 0, node = 0;
  if (!countedge.empty()) remain = countedge.begin()->first, node = countedge.begin()->second;
  
  std::vector<pr <int,int> > ans;
  
  int lst = -1;
  if (remain) ans.push_back(mp(node, node)), -- remain, lst = node;
  for (int i = 0, sz = edge.size(); i < sz; ++ i) {
    pr<int, int> x = edge[i];
    if (abs(lst) == abs(x.first)) std::swap(x.first, x.second);
    if (x.first < 0) {
      ans.push_back(mp(-x.first, -x.first));
      ans.push_back(mp(-x.second, -x.second));
    } else {
      ans.push_back(x);
    }
    lst = abs(abs(x.second));

    if (remain && lst != node) {
      ans.push_back(mp(node, node));
      -- remain;
      lst = node;
    }
  }
  if (remain && lst != node) ans.push_back(mp(node, node)), -- remain, lst = node;

  if (ans.size() != n) std::cout << "No\n";
  else {
    std::cout << "Yes\n";
    for (auto x: ans) std::cout << data[x.first] << " " << data[x.second] << "\n";
  }
  return 0;
}
/*
4
1 2
1 1
1 1
2 2

3
2 4
3 3
3 3
*/

E

Tire,概率 DP。

第一次看到还有这种概率 DP 的哦哦牛批

把问题放到 01 Trie 上考虑,发现问题等价于选择 \(n\) 条自底到终止状态的路径,每条边概率任意分配为 \(p\)\(1-p\),最大化所有边上概率的乘积。

乘积显然可以分开考虑每条边的影响,于是考虑每个节点 \(u\) 向两个子节点的转移边数量,设子树 \(u\) 中有终止状态数 \(\operatorname{sz}_u\),则对于节点 \(u\),在此处需要分别向左右子节点分别转移 \(\operatorname{sz}_{\operatorname{son}_0}\)\(\operatorname{sz}_{\operatorname{son}_1}\) 次,实际意义即节点 \(u\) 代表的前缀+ '0' 会作为 \(\operatorname{sz}_{\operatorname{son}_0}\) 个串的前缀,+ '1' 会作为 \(\operatorname{sz}_{\operatorname{son}_1}\) 个串的前缀。

考虑最大化每个节点 \(u\) 所有转移的概率之积。记 \(F(x, y)\) 表示打出 \(x\)'0'\(y\)'1' 的最大概率,显然有:

\[\forall 0\le x, y\le n,\ F(x, 0) = px, F(0, y) = py \]

\[\forall 1\le x, y\le 1,\ F(x, y) = \max(pF(x - 1, y) +(1-p)F(x, y - 1), pF(x, y - 1) + (1-p)F(x - 1, y)) \]

\(F\) 可以 \(O(n^2)\) 地直接预处理,则节点 \(u\) 的贡献即为 \(F(\operatorname{sz}_{\operatorname{son}_0}, \operatorname{sz}_{\operatorname{son}_1})\),答案即为:

\[\prod_{u\in T} F(\operatorname{sz}_{\operatorname{son}_0}, \operatorname{sz}_{\operatorname{son}_1}) \]

建 Trie 后在上面 dfs 实现即可。总时间复杂度 \(O(n^2 + nm)\) 级别。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1010;
//=============================================================
int n, m;
double p, ans, f[kN][kN];
//=============================================================
namespace trie {
  const int kNode = kN * kN;
  int rt = 1, nodenum = 1, sz[kNode], tr[kNode][2];
  void insert(std::string &s_) {
    int now = rt, len = s_.length();
    for (auto ch: s_) {
      if (!tr[now][ch ^ '0']) tr[now][ch ^ '0'] = ++ nodenum;
      now = tr[now][ch ^ '0'];   
    }
    ++ sz[now];
  }
  void dfs(int u_) {
    for (int i = 0; i < 2; ++ i) {
      if (!tr[u_][i]) continue;
      dfs(tr[u_][i]);
      sz[u_] += sz[tr[u_][i]];
    }
    ans *= f[sz[tr[u_][0]]][sz[tr[u_][1]]];
  }
}
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  std::cin >> n >> m >> p;
  f[0][0] = 1;
  for (int i = 0; i <= n; ++ i) {
    for (int j = 0; j <= n; ++ j) {
      if (i == 0 && j == 0) continue;
      double v1 = 0, v2 = 0;
      if (i > 0) v1 += p * f[i - 1][j], v2 += (1 - p) * f[i - 1][j];
      if (j > 0) v1 += (1 - p) * f[i][j - 1], v2 += p * f[i][j - 1];
      f[i][j] = std::max(v1, v2);
      // std::cout << i << " " << j << " " << std::fixed << std::setprecision(10) << f[i][j] << "\n";
    }
  }

  for (int i = 1; i <= n; ++ i) {
    std::string s; std::cin >> s;
    trie::insert(s);
  }
  ans = 1;
  trie::dfs(1);
  std::cout << std::fixed << std::setprecision(10) << ans << "\n";
  return 0;
}

写在最后

学到了什么:

  • A:妈的
  • D:先考虑简单的子情况,再考虑能否扩展。
posted @ 2024-07-23 18:32  Luckyblock  阅读(535)  评论(1编辑  收藏  举报