JOI Open 2016
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;
}
JOI Open 2016的更多相关文章
- [LOJ#2743][DP]「JOI Open 2016」摩天大楼
题目传送门 DP 经典题 考虑从小到大把数加入排列内 如下图(\(A\) 已经经过排序): 我们考虑如上,在 \(i\) ( \(A_i\) )不断增大的过程中,维护上面直线 \(y=A_i\) 之下 ...
- [题解] [LOJ2743]「JOI Open 2016」摩天大楼
题目大意 将 \(N\) 个互不相同的整数 \(A_1 , A_2 , ⋯ , A_N\) 任意排列成 \(B_1 , B_2 , ⋯ , B_N\) . 要求 \(∑^{N−1}_{i=1} |B_ ...
- 「JOI 2016 Final」断层
嘟嘟嘟 今天我们模拟考这题,出的是T3.实在是没想出来,就搞了个20分暴力(还WA了几发). 这题关键在于逆向思维,就是考虑最后的\(n\)的个点刚开始在哪儿,这样就减少了很多需要维护的东西. 这就让 ...
- Luogu P5103 「JOI 2016 Final」断层 树状数组or线段树+脑子
太神仙了这题... 原来的地面上升,可以倒着操作(时光倒流),转化为地面沉降,最后的答案就是每个点的深度. 下面的1,2操作均定义为向下沉降(与原题意的变换相反): 首先这个题目只会操作前缀和后缀,并 ...
- LOJ#2343. 「JOI 2016 Final」集邮比赛 2
题目地址 https://loj.ac/problem/2343 题解 首先处理出\(f[i]\)表示以当前位置开头(J,O,I)的合法方案数.这个显然可以\(O(n)\)处理出来.然后考虑在每个位置 ...
- Loj #2731 「JOISC 2016 Day 1」棋盘游戏
Loj 2731 「JOISC 2016 Day 1」棋盘游戏 JOI 君有一个棋盘,棋盘上有 \(N\) 行 \(3\) 列 的格子.JOI 君有若干棋子,并想用它们来玩一个游戏.初始状态棋盘上至少 ...
- Be Better:遇见更好的自己-2016年记
其实并不能找到好的词语来形容过去的一年,感觉就如此平淡的过了!没有了毕业的稚气,看事情淡了,少了一丝浮躁,多了一分认真.2016也许就是那句话-多读书,多看报,少吃零食多睡觉,而我更愿意说--Be B ...
- Connect() 2016 大会的主题 ---微软大法好
文章首发于微信公众号"dotnet跨平台",欢迎关注,可以扫页面左面的二维码. 今年 Connect 大会的主题是 Big possibilities. Bold technolo ...
- “.Net 社区虚拟大会”(dotnetConf) 2016 Day 3 Keynote: Scott Hanselman
美国时间 6月7日--9日,为期三天的微软.NET社区虚拟大会正式在 Channel9 上召开,美国时间6.9 是第三天, Scott Hanselman 做Keynote.今天主题围绕的是.NET ...
- “.Net 社区虚拟大会”(dotnetConf) 2016 Day 2 Keynote: Miguel de Icaza
美国时间 6月7日--9日,为期三天的微软.NET社区虚拟大会正式在 Channel9 上召开,美国时间6.8 是第二天, Miguel de Icaza 做Keynote,Miguel 在波士顿Xa ...
随机推荐
- 【Android】虚拟设备运行BUG
虚拟设备是AndroidStudio提供的一个真机模拟运行环境 跑这个虚拟设备要下载手机系统镜像才能跑起来 然后项目中勾选这个虚拟设备,怎么设置就不赘述了 问题奇怪的是运行环境有了,App应用程序也能 ...
- 【Spring-Security】Re02 基础认证流程
一.权限认证模拟操作: 编写Security配置类: package cn.zeal4j.configuration; import org.springframework.context.annot ...
- 【FastDFS】环境搭建 01 跟踪器和存储节点
FastDFS:分布式文件系统 它对文件进行管理,功能包括:文件存储.文件同步.文件访问(文件上传.文件下载)等,解决了大容量存储和负载均衡的问题. 特别适合以文件为载体的在线服务,如相册网站.视频网 ...
- 【TypeScript】01 基础入门
前提:使用TypeScript你需要安装NodeJS支持 然后安装TypeScript: npm intsall -g typescript 安装完成后查看版本号: tsc -v 新建一个TypeSc ...
- 【转载】 AI与人类首次空战,5:0大胜!40亿次模拟造美国怪兽,谁与争锋? (再次证明深度强化学习路线的正确性)
原文: https://mbd.baidu.com/newspage/data/landingsuper?context=%7B%22nid%22%3A%22news_1003478953355572 ...
- Ubuntu18.04下 修改conda环境和缓存默认路径
查看conda 的默认环境和缓存默认路径:conda info conda环境和缓存的默认路径(envs directories 和 package cache) envs directories ...
- 【转载】 详解nohup /dev/null 2>&1 含义的使用
原文地址: https://www.jb51.net/article/169837.htm ==================================== 这篇文章主要介绍了详解nohup ...
- Camera | 6.v4l2拓扑架构
一. 设备节点.模块.拓扑结构关系 拓扑结构是我们了解MIPI-CSI内部模块以及与摄像头连接关系的最直观最便捷的方法. 1. 如何表示拓扑结构? file视角 v4l2视角 来自: 参考文档< ...
- ollama搭建本地ai大模型并应用调用
1.下载ollama 1)https://ollama.com 进入网址,点击download下载2)下载后直接安装即可. 2.启动配置模型 默认是启动cmd窗口直接输入 1 ollama run l ...
- Win32_SDK 屏蔽Edit控件的右键系统菜单方法
找了好久的方法,网上都是基于mfc的方法,现在找到解决方法了,分享给大家, 就是要重新设置Edit控件的回调函数 //Win32 SDK 下Edit控件屏蔽右键系统菜单方法 第一步: //声明保存旧的 ...