鱼C论坛

 找回密码
 立即注册
查看: 2344|回复: 12

[心之碎片] - 20240814模拟赛

[复制链接]
发表于 2024-8-21 22:54:18 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 柿子饼同学 于 2024-8-21 22:58 编辑
总结与反思
B - 要反过来考虑贡献
C - 换根题通常想分类, 然后对于每个点求一个子树的答案, 一个子树外的答案, 树上问题一定要画图, 分类
D - dfs填数独实际上是对于一个点的队列来填, 只需要安排好搜索顺序,
带着全局变量的搜索搜到答案一定要给下一个赋值为现在这个, 因为回溯不一定是从头开始
警惕排序等会让位置变化的东西, 位置会变化导致无法按照原来的编号查询


B - 加号
给定一个长度为 n 的数字字符串,每个字符是'0'~'9'。
现在有k个加号,可以插入到字符串中,每个加号只能在两个数字之间。
每一种插入方案,都对应一个表达式,可以计算出一个值。
求所有方案的值之和。

插入加号的具体不好算, 考虑计算每个数字作为个位/十位...的总贡献, 这样就使得她后面的几个位置不能填 + , 其他位置随便
所以预处理组合数, 上前缀和

  1. // sum[i] 表示 1 为第 i 位的贡献
  2. // 垒前缀和即可 O(1) 计算
  3. for (int i = 1; i <= n; i++) {
  4.     sum[i] = sum[i - 1];
  5.     sum[i] = sum[i] + math.p10[i - 1] % P * math.C(n - i - 1, k - 1) % P;
  6. }
  7. // 注意考虑 i 这个位置后面不放 +, 要单独算
  8. for (int i = 1; i <= n; i++) {
  9.     ans = (ans + (s[i] * (math.p10[n - i] * math.C(i - 1, k) % P + sum[n - i]) % P)) % P;
  10. }

  11. cout << ans;
复制代码


C - 疯狂动态树
换根 + 查询子树内的点到根的距离和 + 查询链上的点到根的距离和

分类讨论, 对于子树问题分类 : 根在 u 上, u 的子树内, u 的子树外
对于链, 求出根到链的最短路和接入根的点, 然后路径分成两半求个等差数列

对于点到链的两端加上这个链, 三个路径的交点是三个点的 lca 中去掉两个相同的值
  1. #include <bits/stdc++.h>
  2. using namespace std;

  3. const int N = 1e5 + 5;

  4. int ID, n, m, rt;
  5. vector<int> e[N];

  6. // up, dn 分别代表除了子树, 子树内的节点到 x 的距离和
  7. int siz[N], up[N], dn[N], dep[N], dfn[N], tot;
  8. int f[N][18];

  9. void dfs1(int u, int pa) {
  10.     dfn[u] = ++tot;
  11.     siz[u] = 1;
  12.     f[u][0] = pa;
  13.     dep[u] = dep[pa] + 1;

  14.     for (int i = 1; i < 18; i++) {
  15.         f[u][i] = f[f[u][i - 1]][i - 1];
  16.     }

  17.     for (auto v : e[u]) {
  18.         if (v == pa)
  19.             continue;

  20.         dfs1(v, u);
  21.         siz[u] += siz[v];
  22.         dn[u] += siz[v] + dn[v];
  23.     }
  24. }

  25. void dfs2(int u, int pa) {
  26.     // pa 头顶上的 up[pa] 每个点都要走 pa-> u 一次, 总共 n - siz[pa] 次
  27.     // pa 先去掉 u 子树的答案, 再加上除了 u 子树的点从 pa -> u 的次数, 即
  28.     // up[u] = (up[pa] + (n - siz[pa])) + (dn[pa] - dn[u] - siz[u] + siz[pa] - siz[u]);

  29.     if (u != 1)
  30.         up[u] = (n - siz[u]) + up[pa] + dn[pa] - dn[u] - siz[u];

  31.     for (auto v : e[u]) {
  32.         if (v == pa)
  33.             continue;
  34.         dfs2(v, u);
  35.     }
  36. }

  37. int Up(int x, int d) {
  38.     for (int i = 17; i >= 0; i--) {
  39.         if ((d >> i) & 1)
  40.             x = f[x][i];
  41.     }
  42.     return x;
  43. }

  44. int lca(int u, int v) {
  45.     if (dep[u] < dep[v])
  46.         swap(u, v);

  47.     u = Up(u, dep[u] - dep[v]);

  48.     if (u == v)
  49.         return u;

  50.     for (int i = 17; i >= 0; i--) {
  51.         if (f[u][i] == f[v][i])
  52.             continue;
  53.         u = f[u][i];
  54.         v = f[v][i];
  55.     }

  56.     return f[u][0];
  57. }

  58. int dist(int u, int v) { return (dep[u] + dep[v] - dep[lca(u, v)] * 2); }

  59. int sigma(int x) { return ((1 + x) * x / 2); }

  60. bool issub(int x, int y) { return (dfn[y] >= dfn[x] && dfn[y] < dfn[x] + siz[x]); }

  61. int Query_path(int u, int v) {
  62.     // cut 意为 rt 到路径的最近点
  63.     int cut = lca(u, rt) ^ lca(v, rt) ^ lca(u, v);
  64.     int res = 0;

  65.     res += sigma(dist(u, cut)) + sigma(dist(v, cut));
  66.     res += dist(rt, cut) * (dist(u, v) + 1);

  67.     return res;
  68. }

  69. int Query_subt(int u) {
  70.     if (rt == u) {
  71.         return (up[u] + dn[u]);
  72.     }

  73.     if (issub(u, rt)) {
  74.         int c = Up(rt, dep[rt] - dep[u] - 1);
  75.         return (up[u] + dn[u] - dn[c] - siz[c] + dist(rt, u) * (n - siz[c]));
  76.     }

  77.     return (dn[u] + dist(rt, u) * siz[u]);
  78. }

  79. int main() {
  80.     ios::sync_with_stdio(0);
  81.     cin.tie(0);

  82.     freopen("crazy.in", "r", stdin);
  83.     freopen("crazy.out", "w", stdout);

  84.     rt = 1;
  85.     cin >> ID >> n >> m;
  86.     for (int i = 1; i < n; i++) {
  87.         int u, v;
  88.         cin >> u >> v;
  89.         e[u].emplace_back(v);
  90.         e[v].emplace_back(u);
  91.     }

  92.     dfs1(1, 0);
  93.     dfs2(1, 0);

  94.     while (m--) {
  95.         int op, x, y;
  96.         cin >> op >> x;

  97.         if (op == 0) {
  98.             rt = x;
  99.         } else if (op == 1) {
  100.             cin >> y;
  101.             cout << Query_path(x, y) << '\n';
  102.         } else {
  103.             cout << Query_subt(x) << '\n';
  104.         }
  105.     }

  106.     return 0;
  107. }
复制代码


D - 聪明格
n*n 的棋盘, 每个格子填 1-n 的数, 每行每列没有重复数字, 满足给出的连通块中的数字乘积是连通块的值(mp上的值)
求字典序最小的解

搜索优化: 必须满足每行每列不冲突, 按照连通块排序(从好填到不好填, 按照连通块大小和乘积因数个数排序)


  1. #include <bits/stdc++.h>
  2. using namespace std;

  3. typedef pair<int, int> pii;

  4. const int N = 12;
  5. const int M = 305;
  6. const int dx[] = { -1, 0, 1, 0 };
  7. const int dy[] = { 0, 1, 0, -1 };

  8. int mp[N][N], n;

  9. // 首先给连通块排序, 重新分配每个点的搜索顺序, 确定每个点属于的连通块编号
  10. struct Block {
  11.     int siz, facs;
  12.     vector<pii> node;
  13. } blk[N * N];

  14. int cnt_blk, id[N][N];
  15. // 注意 blk 是要排序的, 后面没法直接查询 siz, 所以这里记录一下
  16. int siz[N * N];

  17. int Factors(int x) {
  18.     int res = 0;
  19.     for (int i = 1; i * i <= x; i++) {
  20.         if (!(x % i))
  21.             res++;
  22.     }
  23.     return res;
  24. }

  25. bool Check(int x, int y) { return (x > 0 && x <= n && y > 0 && y <= n && !id[x][y]); }

  26. void Explore(int x, int y) {
  27.     id[x][y] = cnt_blk;
  28.     blk[cnt_blk].siz++;
  29.     blk[cnt_blk].node.emplace_back(x, y);

  30.     for (int i = 0; i < 4; i++) {
  31.         int nx = x + dx[i], ny = y + dy[i];
  32.         if (Check(nx, ny) && mp[x][y] == mp[nx][ny]) {
  33.             Explore(nx, ny);
  34.         }
  35.     }
  36. }

  37. vector<pii> q;
  38. int m;

  39. void Assign_points() {
  40.     for (int i = 1; i <= n; i++) {
  41.         for (int j = 1; j <= n; j++) {
  42.             if (!id[i][j]) {
  43.                 cnt_blk++;
  44.                 blk[cnt_blk].facs = Factors(mp[i][j]);
  45.                 Explore(i, j);
  46.                 siz[cnt_blk] = blk[cnt_blk].siz;
  47.             }
  48.         }
  49.     }

  50.     sort(blk + 1, blk + cnt_blk + 1, [](Block& x, Block& y) {
  51.         if (x.siz == y.siz)
  52.             return x.facs < y.facs;
  53.         return x.siz < y.siz;
  54.     });

  55.     for (int i = 1; i <= cnt_blk; i++) {
  56.         for (auto x : blk[i].node) {
  57.             q.emplace_back(x);
  58.         }
  59.     }

  60.     m = q.size();
  61. }

  62. // 现在开始 dfs 填数, 记录答案编号方便回答字典序的要求
  63. bitset<N> visx[N], visy[N];
  64. int cnt[N * N], mul[N * N];
  65. int ans[M][N][N], sum = 0;
  66. set<int> st;

  67. bool Check(int x, int y, int a) { return (!visx[x][a] && !visy[y][a] && !((mp[x][y] / mul[id[x][y]]) % a)); }

  68. void dfs(int pos) {
  69.     if (pos == m) {
  70.         st.emplace(sum++);
  71.         // 一定要注意这里, 当前走成功了之后要给接下来的图赋值
  72.         // 因为下一次走成功不一定是从头开始走的, 可能某一个小分支就又有了
  73.         for (int i = 1; i <= n; i++) {
  74.             for (int j = 1; j <= n; j++) {
  75.                 ans[sum][i][j] = ans[sum - 1][i][j];
  76.             }
  77.         }
  78.         return;
  79.     }

  80.     int x = q[pos].first, y = q[pos].second;
  81.     int c = id[x][y];

  82.     cnt[c]++;
  83.     for (int i = 1; i <= n; i++) {
  84.         // 能取吗?
  85.         if (Check(x, y, i)) {
  86.             mul[c] *= i;
  87.             // 真能取吗?
  88.             if (cnt[c] < siz[c] || (cnt[c] == siz[c] && mul[c] == mp[x][y])) {
  89.                 ans[sum][x][y] = i;
  90.                 visx[x][i] = 1, visy[y][i] = 1;

  91.                 dfs(pos + 1);

  92.                 visx[x][i] = 0, visy[y][i] = 0;
  93.             }
  94.             mul[c] /= i;
  95.         }
  96.     }
  97.     cnt[c]--;
  98. }

  99. void Solve() {
  100.     for (int i = 0; i <= n * n; i++) {
  101.         mul[i] = 1;
  102.     }
  103.     dfs(0);
  104. }

  105. int Select() {
  106.     for (int i = 1; i <= n; i++) {
  107.         for (int j = 1; j <= n; j++) {
  108.             int minv = 1e9;

  109.             for (auto v : st) {
  110.                 minv = min(minv, ans[v][i][j]);
  111.             }

  112.             for (auto it = st.begin(); it != st.end();) {
  113.                 if (ans[*it][i][j] > minv) {
  114.                     it = st.erase(it);
  115.                 } else {
  116.                     it++;
  117.                 }
  118.             }
  119.         }
  120.     }

  121.     return *st.begin();
  122. }

  123. int main() {
  124.     ios::sync_with_stdio(0);
  125.     cin.tie(0);

  126.     freopen("smart.in", "r", stdin);
  127.     freopen("smart.out", "w", stdout);

  128.     cin >> n;
  129.     for (int i = 1; i <= n; i++) {
  130.         for (int j = 1; j <= n; j++) {
  131.             cin >> mp[i][j];
  132.         }
  133.     }

  134.     Assign_points();
  135.     Solve();
  136.     int best = Select();

  137.     cout << sum << '\n';
  138.     for (int i = 1; i <= n; i++) {
  139.         for (int j = 1; j <= n; j++) {
  140.             cout << ans[best][i][j] << ' ';
  141.         }
  142.         cout << '\n';
  143.     }

  144.     return 0;
  145. }
复制代码


评分

参与人数 1荣誉 +3 鱼币 +5 贡献 +3 收起 理由
~风介~ + 3 + 5 + 3 用心了

查看全部评分

本帖被以下淘专辑推荐:

小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 2024-8-21 23:00:57 | 显示全部楼层
鱼油你好,以下是你提到的四道题目的总结与思路解析:

### B - 加号问题
**问题描述**:给定一个长度为 `n` 的数字字符串,每个字符是'0'~'9'。现在有 `k` 个加号,可以插入到字符串中,每个加号只能在两个数字之间。要求所有方案的值之和。

**解题思路**:
1. **计算贡献**:每个数字在表达式中的位置会决定它的贡献,考虑每个数字作为个位、十位等位置的贡献。
2. **使用前缀和与组合数进行预处理**:
   - 用前缀和快速计算子字符串的和。
   - 用组合数计算插入加号的方案数。
3. **复杂度控制**:通过预处理和有效的计算方式,确保算法在 O(n) 或 O(n log n) 的时间内完成。

  1. // 伪代码简化
  2. int calculate_total_value(string s, int k) {
  3.     int n = s.size();
  4.     vector<long long> prefix_sum(n + 1, 0);
  5.     long long total_value = 0;
  6.    
  7.     // 计算前缀和
  8.     for (int i = 1; i <= n; ++i) {
  9.         prefix_sum[i] = prefix_sum[i - 1] * 10 + (s[i - 1] - '0');
  10.     }
  11.    
  12.     // 计算每个位置的贡献
  13.     for (int i = 0; i < n; ++i) {
  14.         long long place_value = 1;
  15.         for (int j = i; j < n; ++j) {
  16.             total_value += (s[j] - '0') * place_value;
  17.             place_value *= 10;
  18.         }
  19.     }
  20.    
  21.     // 返回总值
  22.     return total_value;
  23. }
复制代码


### C - 换根树问题
**问题描述**:给定树和查询,每次询问需要计算某些节点的属性,比如子树的大小、路径上的权值等。

**解题思路**:
1. **分类处理**:每个查询根据性质分类,如子树内部和外部的计算。
2. **双DFS和LCA处理**:通过递归深度优先搜索(DFS),一次计算子树属性,二次根据换根情况重新计算。

  1. // 伪代码简化
  2. int lca(int u, int v) {
  3.     // 找到u和v的最近公共祖先
  4. }

  5. // 距离计算
  6. int dist(int u, int v) {
  7.     return dep[u] + dep[v] - 2 * dep[lca(u, v)];
  8. }

  9. // 处理每次查询的主函数
  10. int Query_path(int u, int v) {
  11.     int cut = lca(u, rt) ^ lca(v, rt) ^ lca(u, v);
  12.     int res = 0;
  13.     // 计算路径上的贡献
  14.     return res;
  15. }
复制代码


### D - 深度优先搜索填数独
**问题描述**:使用深度优先搜索(DFS)来填数独,确保每个填充操作的正确性。

**解题思路**:
1. **队列填充**:每次选择一个点进行填充,根据可能的选项填入。
2. **搜索顺序设计**:安排好回溯顺序,确保不重复或遗漏,尤其要注意全局变量的影响,每次搜索完成要更新状态。

  1. // 伪代码简化
  2. void dfs_fill_sudoku(...) {
  3.     if (complete) return;
  4.     for (int i = 0; i < n; ++i) {
  5.         // 尝试填入每个可能的数字
  6.         dfs_fill_sudoku(...);
  7.     }
  8. }
复制代码


### 反思与总结
- **性能优化**:在处理大规模数据时,需通过优化算法设计和使用合适的数据结构确保效率。
- **边界条件**:考虑各种输入的极端情况,确保算法能正确处理。
- **调试与测试**:多测试用例覆盖,尤其是边界和特殊情况,防止意外错误。

希望这些简洁的解释对你有所帮助!如果有更多问题或需要进一步的代码实现,请随时提出。

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2024-8-22 08:57:54 | 显示全部楼层

回帖奖励 +3 鱼币

小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 2024-8-22 20:23:58 | 显示全部楼层

回帖奖励 +3 鱼币

666
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 2024-8-23 09:25:55 | 显示全部楼层
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 2024-8-23 09:36:02 | 显示全部楼层

回帖奖励 +3 鱼币

小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 2024-8-23 16:18:23 | 显示全部楼层

回帖奖励 +3 鱼币

小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 2024-8-23 16:21:44 | 显示全部楼层

回帖奖励 +3 鱼币

小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 2024-8-24 11:46:30 | 显示全部楼层

回帖奖励 +3 鱼币

&#128076;
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2024-8-29 09:24:20 | 显示全部楼层

回帖奖励 +3 鱼币

/orz/orz
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2024-9-1 10:04:31 | 显示全部楼层

回帖奖励 +3 鱼币

厉害了楼主
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2024-9-4 17:17:58 | 显示全部楼层

回帖奖励 +3 鱼币

谢谢
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 2024-9-14 17:42:43 | 显示全部楼层

回帖奖励 +3 鱼币

谢谢鱼币
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

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

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-4-27 16:53

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表