ABC237

F

题目大意

求长度为 \(n\) 且最长上升子序列长度恰好为 \(3\) 的序列 \(a\) 的个数(\(1 \leq a_i \leq m\))。

解题思路

不难判断这道题是一道线性 dp。

先考虑求最长上升子序列的过程:可以把线性扫描序列理解为一个不断在这个序列后加数的过程。

当最长上升子序列被更新时当且仅当加的这个数大于原最长上升子序列的最后一项。

考虑到原来的最长上升子序列最后一项可能会有多值,我们可以只保留最小值,使得任意能更新最长上升子序列的数都必定大于这个值。

又发现题目中只要求维护的最长上升子序列长度为 \(3\),且 \(a_i\) 范围很小,我们便可以在 \(dp\) 中用三维分别表示长度为 \(1\)、\(2\)、\(3\) 的最长上升子序列末尾最小值。

于是我们便可以设计以下的动态规划:

  • 状态表示:\(dp[i][x][y][z]\) 表示长度为 \(i\),长度为 \(1\) 的上升子序列最后一项的最小值为 \(x\),为 \(2\) 的为 \(y\) ,为 \(3\) 的为 \(z\)(编码技巧:可以用 \(m+1\) 表示不存在)。
  • 初始化:\(dp[0][m + 1][m + 1][m + 1]=1\)。
  • 状态转移:原来的三个数为 \(x\) 、 \(y\) 、 \(z\) ,现加入一个数 \(j\),将第一个比 \(j\) 大的数换成 \(j\) 即可(注意当且仅当 \(x \leq y \leq z\) 时转移,否则无意义)。
\[\begin{cases}
dp[i][j][y][z]=dp[i][j][y][z] + dp[i - 1][x][y][z]\text{, $j \leq x$}\\
dp[i][x][j][z]=dp[i][x][j][z] + dp[i - 1][x][y][z]\text{, $x < j \leq y$}\\
dp[i][x][y][j]=dp[i][x][y][j] + dp[i - 1][x][y][z]\text{, $y < j \leq z$}
\end{cases}
\]
  • 答案:满足 \(x<y<z\) 的 \(dp[n][x][y][z]\) 的和。

代码

  1. #include<bits/stdc++.h>
  2. #define endl "\n"
  3. #define ll long long
  4. using namespace std;
  5. const int N = 1010, P = 998244353;
  6. int n, m;
  7. ll dp[N][20][20][20], ans;
  8. int main()
  9. {
  10. ios :: sync_with_stdio(0);
  11. cin.tie(0), cout.tie(0);
  12. cin >> n >> m;
  13. dp[0][m + 1][m + 1][m + 1] = 1;
  14. for (int i = 1; i <= n; i++)
  15. {
  16. for (int j = 1; j <= m; j++)
  17. {
  18. for (int x = 1; x <= m + 1; x++)
  19. {
  20. for (int y = x; y <= m + 1; y++)
  21. {
  22. for (int z = y; z <= m + 1; z++)
  23. {
  24. if (j <= x)
  25. {
  26. (dp[i][j][y][z] += dp[i - 1][x][y][z]) %= P;
  27. }
  28. else if (j <= y)
  29. {
  30. (dp[i][x][j][z] += dp[i - 1][x][y][z]) %= P;
  31. }
  32. else if (j <= z)
  33. {
  34. (dp[i][x][y][j] += dp[i - 1][x][y][z]) %= P;
  35. }
  36. }
  37. }
  38. }
  39. }
  40. }
  41. for (int i = 1; i <= m; i++)
  42. {
  43. for (int j = i + 1; j <= m; j++)
  44. {
  45. for (int k = j + 1; k <= m; k++)
  46. {
  47. (ans += dp[n][i][j][k]) %= P;
  48. }
  49. }
  50. }
  51. cout << ans << endl;
  52. return 0;
  53. }

G

题目大意

给出区间升序和区间降序排列两种操作,操作 \(m\) 次后,求原排列中 \(x\) 现在的位置。

解题思路

关于区间排序有一个经典 trick,我们可以将大于 \(x\) 的数标为 \(1\),小于等于 \(x\) 的数标为 0。

为什么?众所周知,排序一次复杂度为 \(O(n\log n)\),难以接受。

但是这么标了之后由于只剩下 \(0\) 和 \(1\),我们便可以使用线段树统计 \(1\) 个数。

令 \(1\) 的个数为 \(sum\)。

  1. 对于操作 \(1\) ,用线段树将 \([l, r - sum]\) 区间推平为 \(0\),将 \([r - sum + 1, r]\) 区间推平为 \(1\),如果此时数 \(x\) 在区间中,就将它更新为 \(r - sum\)。
  2. 对于操作 \(2\) ,用线段树将 \([l, l + sum - 1]\) 区间推平为 \(1\),将 \([l + sum, r]\) 区间推平为 \(0\),如果此时数 \(x\) 在区间中,就将它更新为 \(l + sum\)。

如此操作,单次操作复杂度就可以做到 \(O(\log n)\),一共\(O(m\log n)\),可以通过本题。

代码

  1. #include<bits/stdc++.h>
  2. #define endl "\n"
  3. #define ls x << 1
  4. #define rs x << 1 | 1
  5. using namespace std;
  6. const int N = 2e5 + 10;
  7. int n, m, p, ans;
  8. int a[N];
  9. struct SGT
  10. {
  11. int lc[N << 2], rc[N << 2];
  12. long long dat[N << 2], tag[N << 2];
  13. inline void push_up(int x)
  14. {
  15. dat[x] = dat[ls] + dat[rs];
  16. }
  17. inline void push_down(int x)
  18. {
  19. if (!tag[x])
  20. {
  21. return;
  22. }
  23. if (tag[x] == 1)
  24. {
  25. dat[ls] = rc[ls] - lc[ls] + 1;
  26. dat[rs] = rc[rs] - lc[rs] + 1;
  27. }
  28. else
  29. {
  30. dat[ls] = dat[rs] = 0;
  31. }
  32. tag[ls] = tag[rs] = tag[x];
  33. tag[x] = 0;
  34. return;
  35. }
  36. void build(int x, int l, int r)
  37. {
  38. lc[x] = l, rc[x] = r;
  39. tag[x] = 0;
  40. if (l == r)
  41. {
  42. dat[x] = a[l] > p;
  43. return;
  44. }
  45. int mid = (l + r) / 2;
  46. build(ls, l, mid);
  47. build(rs, mid + 1, r);
  48. push_up(x);
  49. return;
  50. }
  51. void modify(int x, int l, int r, int k)
  52. {
  53. if (l > rc[x] || r < lc[x])
  54. {
  55. return;
  56. }
  57. if (lc[x] >= l && rc[x] <= r)
  58. {
  59. dat[x] = k * (rc[x] - lc[x] + 1);
  60. tag[x] = k ? 1 : -1;
  61. return;
  62. }
  63. push_down(x);
  64. modify(ls, l, r, k);
  65. modify(rs, l, r, k);
  66. push_up(x);
  67. return;
  68. }
  69. long long ask(int x, int l, int r)
  70. {
  71. if (l > rc[x] || r < lc[x])
  72. {
  73. return 0;
  74. }
  75. if (lc[x] >= l && rc[x] <= r)
  76. {
  77. return dat[x];
  78. }
  79. push_down(x);
  80. return ask(ls, l, r) + ask(rs, l, r);
  81. }
  82. } t;
  83. int main()
  84. {
  85. ios :: sync_with_stdio(0);
  86. cin.tie(0), cout.tie(0);
  87. cin >> n >> m >> p;
  88. for (int i = 1; i <= n; i++)
  89. {
  90. cin >> a[i];
  91. if (a[i] == p)
  92. {
  93. ans = i;
  94. }
  95. }
  96. t.build(1, 1, n);
  97. for (int i = 1; i <= m; i++)
  98. {
  99. int op, l, r;
  100. cin >> op >> l >> r;
  101. int sum = t.ask(1, l, r);
  102. if (op == 1)
  103. {
  104. t.modify(1, l, r - sum, 0);
  105. t.modify(1, r - sum + 1, r, 1);
  106. if (l <= ans && ans <= r)
  107. {
  108. ans = r - sum;
  109. }
  110. }
  111. else
  112. {
  113. t.modify(1, l, l + sum - 1, 1);
  114. t.modify(1, l + sum, r, 0);
  115. if (l <= ans && ans <= r)
  116. {
  117. ans = l + sum;
  118. }
  119. }
  120. }
  121. cout << ans << endl;
  122. return 0;
  123. }

ABC237的更多相关文章

  1. 2022年5月5日模拟赛题解与总结(ABC237)

    总结 初一第一,竞赛班第二 还可以,为了照顾提高班来的四个同学放了四个水题,可惜他们做的不是很理想,希望他们下次可以获得满意的成绩 这次做的其实是 AtCoder ABC237 A.Not Overf ...

随机推荐

  1. Python预安装包制作

    预编译安装包 在Linux服务器上,经常会安装Python.Redis.Nginx等服务,不管离线.在线都需要编译.编译之前还需要安装一些依赖的环境,比如,openssl.gcc.g++等,但是mak ...

  2. 狗的名字 ATCOER-ABC-171-C One Quadrillion and One Dalmatians

    狗的名字 ATCOER-ABC-171-C One Quadrillion and One Dalmatians 题目链接 我们可以将名字看成26进制的数,就可以转化为将一个10进制转26进制的数的问 ...

  3. 性能调优、虚拟机、垃圾回收、软硬件协调相关文章和视频 — Part1

    本文由 ImportNew - 顾星竹 翻译自 javacodegeeks.如需转载本文,请先参见文章末尾处的转载要求. ImportNew注:如果你也对Java技术翻译分享感兴趣,欢迎加入我们的 J ...

  4. Linux之定时任务crontab

    一.cron.d增加定时任务 当我们要增加全局性的计划任务时,一种方式是直接修改/etc/crontab.但是,一般不建议这样做,/etc/cron.d目录就是为了解决这种问题而创建的. 例如,增加一 ...

  5. 1分钟学会如何提升PCIe通信速率,基于RK3568J + FPGA国产平台!

    测试数据汇总 表 1 PCIe总线介绍 PCIe,即PCI-Express(peripheral component interconnect express)是一种高速串行计算机扩展总线标准.主要用 ...

  6. elementui半年选择组件

    1.基于elementui开发的半年选择组件 2.调用 <el-halfyear-picker v-model="date" :size="size"&g ...

  7. 【Kotlin】select简介

    1 前言 ​ 协程的 select 是一种用于异步操作的选择器,它允许同时等待多个挂起函数的结果,并在其中一个完成时执行相应的操作. ​ 能够被 select 的事件都是 SelectClause,在 ...

  8. 群晖 MariaDB10 开启远程登录

    ​情况:MariaDB设置了TCP/IP的端口,但是还是无法进行远程访问. 解决方法: 一.使用ssh登录群晖,并进入MariaDB安装目录 cd /volume1/@appstore/MariaDB ...

  9. 技术漫谈|IVR通用开发框架简说

    IVR为Interactive Voice Response的缩写,意为交互式语音应答(系统).它可以应答客户的呼叫,然后为呼叫者提供语音导航或自助服务,呼叫者可通过按键输入或使用语音命令进行选择.随 ...

  10. 【深度学习】Tensorflow学习(1)张量与常用函数

    关于张量 张量可以表示0阶到N阶的数组 在TensorFlow中,张量(Tensor)表示某种相同数据类型的多维数据 因此张量有两个重要特征: 数据类型 数组形状(各个维度的大小) 张量的数据类型 t ...