JOI Open 2016

news/2024/11/15 6:16:50/文章来源:https://www.cnblogs.com/hztmax0/p/18411506

T1 JOIRIS

你在玩俄罗斯方块,游戏区域是一个宽度为 \(n\),高度足够大的矩形网格、初始时第 \(i\) 列有 \(a_i\) 个方块。
给定参数 \(k\),你可以做不超过 \(10^4\) 次操作,来将这个网格中的所有方块全部消除,一次操作形如:

  • 在网格的最顶端落下一个 \(1 \times k\) 或者 \(k \times 1\) 的方块(也就是你可以决定方块是竖着放还是横着放),直到碰到一个方块时停止下落。
  • 自下而上检查所有行,如果一行被方块填满,则消除这一行的所有方块。

需要构造方案。保证 \(1 \le n, a_i \le 50\)

首先注意到我们可以通过若干操作使得 \(a_i \gets a_i \bmod k\)。令 \(t = \lfloor \frac{\max a_i}{k} \rfloor\),通过对于每个位置 \(i\),在 \(i\) 处加入若干竖块,使得 \(a_i \in [tk, (t + 1)k)\),此时至少会消除 \(tk\) 次,于是 \(\forall i, a_i < k\)。我们称这样的操作为一次调整。

考虑 \(a\)\(k\) 意义下的差分数组 \(d_{1 \sim n + 1}\),其满足 \(d_i = a_i - a_{i - 1}\),对于一种合法的终态,由于所有方块将被消空,所以 \(\forall i \in [2, n], d_i = 0\)。注意我们在 \(d_1\)\(d_{n + 1}\) 处无限制。

现在从 \(d\) 的角度观察所有操作,现在加竖块 \(d\) 不变,消除一行 \(d_{2 \sim n}\) 不变,没有影响,唯一对 \(d\) 有影响的操作是在 \(i\) 处加一个横块,其影响为 \(d_i \gets d_i + 1, d_{i + k} \gets d_{i + k} - 1\),那么有解的必要条件是 \(\forall r \in [0, k), 1 \bmod k \neq r, (n + 1) \bmod k \neq r, (\sum\limits_{i \bmod k = r} d_i) \equiv 0 \pmod k\),下面我们通过构造证明其是充分的。

考虑我们一定可以通过如下操作使得 \(d_i \gets d_i + v\)\(d_{i + k} \gets d_{i + k} - v\)\(v \in [0, k)\)):

  • \(i\) 处加入 \(v\) 个横块,在 \(\forall i \in [1, n] - [i, i + k)\) 处加入 \(2\) 个竖块,此时 \(v\) 个横块全部被消除,如果剩下部分存在高度大于 \(k\),即 \(a_i \ge k\) 的位置,按照前面所说的方法调整即可。

那么通过上述操作,对于 \(\bmod k = r\) 分组后 \(\sum d\) 不变,但是对于 \(1\)\(n + 1\) 所在的 \(r\) 我们可以全部丢到 \(1\)\(n + 1\),所以没有前面的限制。

操作次数 \(O(1) \times (n^2 + \sum a_i)\)

Code
#include <iostream>
#include <vector>
#include <algorithm>using namespace std;const int N = 55;int n, k;
int a[N], d[N];
vector<int> v[N];
vector<pair<int, int>> ans;void Adjust () {int mx = 0;for (int i = 1; i <= n; ++i) {mx = max(mx, a[i] / k);}for (int i = 1; i <= n; ++i) {while (a[i] < mx * k) {ans.push_back({1, i});a[i] += k; }a[i] -= mx * k;}
}void Operate (int x, int v) {if (!v) return; d[x] = (d[x] + v) % k; d[x + k] = (d[x + k] - v + k) % k;for (int i = 1; i <= v; ++i) {ans.push_back({2, x});}  for (int i = 1; i <= n; ++i) {if (i < x || i >= x + k) {ans.push_back({1, i});ans.push_back({1, i});a[i] += k * 2 - v; }}Adjust();
}int main () {// freopen("tmp.in", "r", stdin);// freopen("tmp.out", "w", stdout);cin.tie(0)->sync_with_stdio(0);cin >> n >> k;for (int i = 1; i <= n; ++i) {cin >> a[i];}Adjust();for (int i = 1; i <= n + 1; ++i) {d[i] = (a[i] - a[i - 1] + k) % k;v[i % k].push_back(i);}for (int r = 0; r < k; ++r) {if (v[r].empty()) continue;int sum = 0; for (auto i : v[r]) {sum = (sum + d[i]) % k; }if (v[r][0] == 1) {for (int j = v[r].size() - 1; j; --j) {Operate(v[r][j - 1], d[v[r][j]]);}}else {if (sum && v[r].back() != n + 1) {cout << -1 << '\n';return 0; }for (int j = 0; j < v[r].size() - 1; ++j) {int i = v[r][j];Operate(i, (k - d[i]) % k);}}}int mx = *max_element(a + 1, a + n + 1);for (int i = 1; i <= n; ++i) {int cnt = (mx - a[i]) / k; for (int j = 1; j <= cnt; ++j) {ans.push_back({1, i});}}cout << ans.size() << '\n';for (auto i : ans) {cout << i.first << ' ' << i.second << '\n';}return 0; 
}

T2 Selling RNA Strands

题意:给定 \(n\) 个字符串 \(s_{1 \sim n}\),有 \(m\) 次询问,每次询问格式如下:

  • 给出字符串 \(p\)\(q\),求 \(s_{1 \sim n}\) 中有多少个字符串同时以 \(p\) 为前缀,并以 \(q\) 为后缀。

\(N = \max(n, m), S = \max(\sum|s_i|, \sum|p|, \sum|q|)\),保证 \(N \le 10^5, S \le 2 \times 10^6\),字符集大小为 \(4\)

首先考虑如果只有前缀为 \(p\) 的限制是好做的,我们对于 \(s\)\(\text{Trie}\),每次查询时走到 \(p\) 对应的 Trie 上的结点,做子树标记求和即可。

现在加入后缀为 \(p\) 的限制,不难想到对 \(s\) 的反串再建一棵 \(\text{Trie}\),假设一个字符串 \(s\) 在两颗 Trie 上的对应点分别为 \(a\)\(b\),某次查询的前后缀 \(p, q\) 在两颗 \(\text{Trie}\) 上的对应点分别为 \(a_0\)\(b_0\),那么 \(s\) 对该询问有贡献,当且仅当 \(a_0\)\(a\) 的祖先,\(b_0\)\(b_0\) 的祖先。

对于祖先的限制考虑用 \(\text{dfs}\) 序刻画,那么原问题可转化成二维数点,扫描线 + 树状数组即可,时间复杂度 \(O(S + N \log S)\)

Code
#include <iostream>
#include <algorithm>
#include <vector>
#include <numeric>using namespace std;const int N = 1e5 + 5, T = 2e6 + 5; int n, m; 
int a[N], b[N], p[N], ans[N];string Read () {string s;cin >> s;for (auto &c : s)c = (c == 'A' ? 0 : (c == 'G' ? 1 : (c == 'U' ? 2 : 3)));return s;
}struct Trie {int tot;int ch[T][4], dfn[T], siz[T];int Insert (string s) {int k = 0; for (auto c : s) {if (!ch[k][c]) ch[k][c] = ++tot;k = ch[k][c];}return k; }void Dfs (int x) {dfn[x] = ++tot;siz[x] = 1; for (int i = 0, y; i < 4; ++i) {if (y = ch[x][i]) {Dfs(y);siz[x] += siz[y];}}}int Get_id (string s) {int k = 0; for (auto c : s) {k = ch[k][c];if (!k) return -1;}return k;}
} trie, rtrie;struct E {int x, l, r, v, id; 
};struct Bit {int tr[T];void Add (int x, int y) {for (; x <= rtrie.tot; x += (x & -x)) {tr[x] += y;}}int Query (int l, int r) {int res = 0; for (--l; l; l -= (l & -l)) {res -= tr[l];}for (; r; r -= (r & -r)) {res += tr[r];}return res;}
} bit;int main () {cin.tie(0)->sync_with_stdio(0);cin >> n >> m;for (int i = 1; i <= n; ++i) {string s = Read();a[i] = trie.Insert(s);reverse(s.begin(), s.end());b[i] = rtrie.Insert(s);} trie.Dfs(0), rtrie.Dfs(0);for (int i = 1; i <= n; ++i) {a[i] = trie.dfn[a[i]], b[i] = rtrie.dfn[b[i]];}vector<E> v;for (int i = 1; i <= m; ++i) {string s = Read(), t = Read();reverse(t.begin(), t.end());int p = trie.Get_id(s), q = rtrie.Get_id(t);if (p != -1 && q != -1) {int al = trie.dfn[p], ar = trie.dfn[p] + trie.siz[p] - 1, bl = rtrie.dfn[q], br = rtrie.dfn[q] + rtrie.siz[q] - 1; v.push_back(E({al - 1, bl, br, -1, i}));v.push_back(E({ar, bl, br, 1, i}));}} sort(v.begin(), v.end(), [&](E a, E b) -> bool { return a.x < b.x;}); iota(p + 1, p + n + 1, 1);sort(p + 1, p + n + 1, [&](int i, int j) -> bool {return a[i] < a[j];});int t = 1; for (auto i : v) {while (t != n + 1 && a[p[t]] <= i.x) {bit.Add(b[p[t++]], 1);}ans[i.id] += bit.Query(i.l, i.r) * i.v;}for (int i = 1; i <= m; ++i) {cout << ans[i] << '\n';}return 0;  
}

T3 Skyscraper

题意:给定一个长度为 \(n\) 的序列 \(a_i\),满足 \(a\) 中元素两两不相同。
给定 \(L\),计数有多少个 \(a\) 的排列 \(p\),满足 \(\sum\limits_{i = 1}^{n - 1} |p_i - p_{i + 1}| \le L\)
\(1 \le n \le 10^2, 1 \le a_i, L \le 10^3\)

考虑 \(\sum\limits_{i = 1}^{n - 1} |p_i - p_{i + 1}| \le L\) 的限制,显然正着是不好做的。套路的可以想到从上往下扫描线,维护连续段的数量。那么我们的要求就是线段长度总和 \(\le L\)

假设我们从大往小加数,假设扫描线的上一个位置为 \(lst\),当前这个数所在位置为 \(cur\),那么可能会有若干未闭合的插头,每个会对线段长度总和造成 \(lst - cur\) 的贡献。我们发现这样的插头数量至于当前的连续段数有关,于是我们并不关心当前加入的所有数的形态,而只需记录有多少个连续段。

考虑一个问题,如果一个数在边界,那么它会少贡献一个插头,如右上图所示。但是这个是好处理的,我们只需再记录位于左右边界上的数是否已经确定即可。

默认滚动数组消去当且加的是第几个数的维度。状态就是 \(f_{i, j, p, q}\),表示当前有 \(i\) 个连续段,线段总长为 \(j\)\(p, q\) 表示是否钦定了最左和最右的数的方案数。

转移有三种情况:新创建一个段、延续一个段、合并相邻的两个段,分类讨论一下即可。

时间复杂度 \(O(n^2L)\)

Code
#include <iostream>
#include <algorithm>using namespace std;const int N = 105, M = 1e3 + 5; 
const int Mod = 1e9 + 7; int n, lim;
int a[N], f[N][M][2][2], g[N][M][2][2];void Add (int &x, int y) {x = ((x += y) >= Mod ? x - Mod : x);
}int main () {cin.tie(0)->sync_with_stdio(0);cin >> n >> lim;for (int i = 1; i <= n; ++i) {cin >> a[i];}sort(a + 1, a + n + 1, greater<int>());for (int i = 0; i < 2; ++i) for (int j = 0; j < 2; ++j)f[1][0][i][j] = 1; for (int i = 1; i < n; ++i) {for (int j = 1; j <= i; ++j) {for (int k = 0; k <= lim; ++k) {for (int p = 0; p < 2; ++p) {for (int q = 0; q < 2; ++q) {g[j][k][p][q] = f[j][k][p][q], f[j][k][p][q] = 0; }}}}for (int j = 1; j <= i; ++j) {for (int k = 0; k <= lim; ++k) {for (int p = 0; p < 2; ++p) {for (int q = 0; q < 2; ++q) {int v = g[j][k][p][q];if (!v)continue;int _k = k + (j * 2 - p - q) * (a[i] - a[i + 1]);if (_k > lim) continue;Add(f[j + 1][_k][p][q], 1ll * (j - 1) * v % Mod);if (!p) {Add(f[j + 1][_k][0][q], v);Add(f[j + 1][_k][1][q], v);}if (!q) { Add(f[j + 1][_k][p][0], v);Add(f[j + 1][_k][p][1], v);}Add(f[j][_k][p][q], 2ll * (j - 1) * v % Mod);if (!p) {Add(f[j][_k][0][q], v);Add(f[j][_k][1][q], v);}if (!q) {Add(f[j][_k][p][0], v);Add(f[j][_k][p][1], v);}if (j > 1) {Add(f[j - 1][_k][p][q], 1ll * (j - 1) * v % Mod);}}}}}}int ans = 0; for (int i = 0; i <= lim; ++i) {ans = (ans + f[1][i][1][1]) % Mod;}cout << ans << '\n';return 0; 
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/796733.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

章12——异常exception

异常 快捷键 ctrl + alt + t 选中 try-catch 如果进行异常处理,即使出现了异常,程序可以继续执行。异常介绍 开发过程中的语法错误和逻辑错误不是异常。 执行过程中所发生的异常事件可分为如下两大类:异常体系图小结:常见的运行时异常没有关联的类不能进行上下转型 异常处理…

APB总线总结

APB总结 一、简介 APB提供了一个低功耗的接口,并降低了接口的复杂性。APB接口用在低带宽和不需要高性能总线的外围设备上。APB是非流水线结构,所有的信号仅与时钟上升沿相关,这样就可以简化APB外围设备的设计流程,每个传输至少耗用两个周期。 二、信号列表信号名 来源 描述…

面试- Web安全

XSS攻击(跨站脚本攻击)XSS预防 < < > >XSRF(CSRF)攻击(跨站请求伪造)就像是你在不知情的情况下,被别人利用你的权限发起了某个你没打算进行的请求。重点是可以把你的用户信息给带过去,你不知不觉就帮我付款了。XSS 是恶意代码“潜伏”在页面上,欺骗你去执行…

k8s 中的 Service 简介【k8s 系列之二】

〇、前言 k8s 集群中的每一个 Pod 都有自己的 IP 地址,那么是不是有 IP 了,访问起来就简单了呢,其实不然。 因为在 k8s 中 Pod 不是持久性的,摧毁重建将获得新的 IP,客户端通过会变更 IP 来访问显然不合理。另外 Pod 还经常会通过多个副本来实现负载均衡,客户端如何高效的…

软工作业二:论文查重系统

这个作业属于哪个课程 <计科22级34班>这个作业要求在哪里 [<作业要求>](个人项目 - 作业 - 计科22级34班 - 班级博客 - 博客园 (cnblogs.com))这个作业的目标 通过实际编程任务,全面提升学生在编程、算法、项目管理、性能优化、代码测试和版本控制等方面的能力,为…

面试-JS基础知识-作用域和闭包

问题this的不同应用场景 手写bind函数 实际开发中闭包的应用场景,举例说明 创建10个<a>标签,点击的时候弹出来对应的序号作用域:某个变量的合法使用范围全局 函数 块级** 自由变量上面图的最里面的红框————a a1 a2都是自由变量,因为都没有被定义。会一层一层往上…

学习高校课程-软件设计模式-软件设计原则(lec2)

软件设计原则Feature of Good Design (1) 优秀设计的特点(一) Code reuse 代码复用 – Challenge: tight coupling between components, dependencies on concrete classes instead of interfaces, hardcoded operations – Solution: design patterns – 挑战:组件之间的紧…

ATTCK红队评估(红日靶场4)

靶场介绍本次靶场渗透反序列化漏洞、命令执行漏洞、Tomcat漏洞、MS系列漏洞、端口转发漏洞、以及域渗透等多种组合漏洞,希望大家多多利用。 环境搭建 机器密码 WEB主机 ubuntu:ubuntuWIN7主机 douser:Dotest123(DC)WIN2008主机 administrator:Test2008网络配置111网段是web的网…

Markdown随笔

冰冻三尺非一日之寒,持之以恒方位始终。 Markdown语法讲解标题一共六级标题分别为Ctrl+1~6: 一级 二级 三级 四级 五级 六级字体 粗体 粗斜体 斜体 删除线引用一个大于号>分割线图片超链接 点击进入百度百科列表 数字加上空格(有序) 点加上空格(无序)表格姓名 性别 年…

tarjan里的定义

强连通分量 - OI Wiki (oi-wiki.org)从以u为根的子树中的任意点出发。单次到达(从这个点指向某个点,有一条边) 的这些点中的dfn的最小值以v为根的子树,包含在以u为根的子树中,low[v]所用的子节点,一定也可以被low[u],这个点一定在以u为根的子树里,所以用low[v] 从u这个…

南沙csp-j/s一对一家教陈老师解题:1317:【例5.2】组合的输出

​【题目描述】排列与组合是常用的数学方法,其中组合就是从n个元素中抽出r个元素(不分顺序且r≤n),我们可以简单地将n个元素理解为自然数1,2,…,n,从中任取r个数。 现要求你用递归的方法输出所有组合。 例如n=5,r=3,所有组合为: 1 2 3 1 2 4 1 2 5 1 3 4 1 …

First day01

Markdown学习 二级标题 字体 Hello World Hello World Hello World Hello World Hello World 引用选择java走上人生巅峰分割线图片 ![截图](C:\Users\邢其俊\Pictures\Screenshots\屏幕截图 2024-09-13 180016.png)超链接 [点击跳转到狂神博客](仓库 - 狂神说 (kuangstudy) - G…