「JXOI2017」数列

题意

九条可怜手上有一个长度为 \(n\) 的整数数列 \(r_i\) ,她现在想要构造一个长度为 \(n\) 的,满足如下条件的整数数列 \(A\) :

  • \(1\leq A_i \leq r_i\) 。
  • 对于任意 \(3 \leq i \leq n\) ,令 \(R\) 为 \(A_1\) 至 \(A_{i-2}\) 中大于等于 \(A_{i-1}\) 的最小值, \(L\) 为 \(A_1\) 至 \(A_{i-2}\) 中小于等于 \(A_{i-1}\) 的最大值。\(A_i\) 必须满足 \(L \leq A_i \leq R\)。如果不存在大于等于 \(A_{i-1}\) 的,那 么 \(R = +\infty\) ;如果不存在小于等于 \(A_{i-1}\) 的,那么 \(L = −\infty\) 。

现在可怜想要知道共有多少不同的数列满足这个条件。两个数列 \(A\) 和 \(B\) 是不同的当且仅当至少存在一个位置 \(i\) 满足 \(A_i \neq B_i\) 。

\(n \le 50, A_i \le 150\)

题解

首先不难发现 \(L_i\) 是单调不降,\(R_i\) 是单调不升的,也就是说 \([L_i, R_i]\) 是不断收缩的。

然后发现 \(A_i\) 会在一定会充当 \(L_{i + 1}\) 或 \(R_{i + 1}\) ,注意 \(A_i \in \{L_i, R_i\}\) 的时候,会同时充当 \(L_{i + 1}, R_{i + 1}\) 。

不难得出一个 \(dp\) 令 \(dp[i][l][r]\) 为到第 \(i\) 个点,下界为 \(l\) 上界为 \(r\) 的方案数。

至于边界,一开始暴力枚举前面两个数取值就行了,讨论三种情况就不赘述了。

然后转移的话我们对于 \([l, r]\) 这段区间只需要枚举 \([l, r]\) 之中的数来转移即可。

具体来说我们假设把 \([l, r]\) 这段区间分成 \([l, p]\) 和 \([p, r]\) 这两种不同的区间,直接更新 \(dp[i + 1][l][p]\) 和 \(dp[i + 1][p][r]\) 就行了,但是会发现 \(p\) 会被更新两次,那么在 \(dp[i + 1][p][p]\) 减去即可。

然后注意当 \(p\) 取到 \(l~or~r\) 的时候,转移的就是 \(dp[i + 1][p][p]\) 了。

最后复杂度就是 \(O(n \max^3(A_i))\) ,常数很小可以跑过。

好像看到了孔爷是 \(O(n \max^2(A_i))\) 的 做法 ,恐怖如斯。。。

其实似乎是把一系列相同转移的转移到一起,用填表法转移就行了。

代码

懒得特判 \(n=1,2\) 的情况了,反正数据中没有。。

#include <bits/stdc++.h>

#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl
#define DEBUG(...) fprintf(stderr, __VA_ARGS__) using namespace std; template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; } inline int read() {
int x(0), sgn(1); char ch(getchar());
for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
return x * sgn;
} void File() {
#ifdef zjp_shadow
freopen ("2273.in", "r", stdin);
freopen ("2273.out", "w", stdout);
#endif
} const int N = 55, M = 155, Mod = 998244353; int n, R[N], dp[N][M][M], maxr; inline void Update(int pos, int l, int r, int mid, int val) {
(dp[pos][l][mid] += val) %= Mod;
(dp[pos][mid][r] += val) %= Mod;
(dp[pos][mid][mid] += Mod - val) %= Mod;
} int main () { File(); n = read(); For (i, 1, n) R[i] = read(); maxr = *max_element(R + 1, R + n + 1) + 1; For (i, 1, R[1]) For (j, 1, R[2]) {
if (i == j) Update(3, i, j, i, 1);
if (i > j) Update(3, 0, i, j, 1);
if (i < j) Update(3, i, maxr, j, 1);
} For (i, 3, n - 1) For (l, 0, maxr) For (r, l, maxr) if (dp[i][l][r])
For (cur, l, min(r, R[i]))
if (cur == l || cur == r) Update(i + 1, cur, cur, cur, dp[i][l][r]);
else Update(i + 1, l, r, cur, dp[i][l][r]); int ans = 0;
For (l, 0, maxr) For (r, 0, maxr)
ans = (ans + 1ll * dp[n][l][r] * max(0, (min(r, R[n]) - max(l, 1) + 1))) % Mod;
printf ("%d\n", ans); return 0; }

「JXOI2017」加法

题意

有一个长度为 \(n\) 的正整数序列 \(A\) 。

一共有 \(m\) 个区间 \([l_i, r_i]\) 和两个正整数 \(a, k\) 。从这 \(m\) 个区间里选出恰好 \(k\) 个区间,并对每个区间执行一次区间加 \(a\) 的操作。(每个区间最多只能选择一次。)

最后最大化 \(\min A_i\) 。

\(n, m \le 2 \times 10^5\)

题解

最大化最小值,基本二分答案跑不掉了。

二分 \(\min A_i\) ,也就是使得 \(\forall A_i \ge mid\) 。

然后考虑如何 \(check\) 。可以贪心,考虑把 \(m\) 个区间放到对应的左端点处。

从左往右依次考虑每个点,贪心选择左端点在之前,右端点尽量远的区间,这样肯定最优。

然后不断加 \(a\) 使得当且 \(A_i \ge mid\) 即可,如果区间不够那么就不可行。

至于具体实现,用堆存储右端点最靠右的点,区间加可以直接差分掉就行了。

复杂度是 \(O((n + m) \log m \log A_i)\) 的,常数不大,跑得很快。

代码

#include <bits/stdc++.h>

#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl
#define DEBUG(...) fprintf(stderr, __VA_ARGS__) using namespace std; template<typename T> inline bool chkmin(T &a, T b) {return b < a ? a = b, 1 : 0;}
template<typename T> inline bool chkmax(T &a, T b) {return b > a ? a = b, 1 : 0;} inline int read() {
int x(0), sgn(1); char ch(getchar());
for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
return x * sgn;
} inline void File() {
#ifdef zjp_shadow
freopen ("2274.in", "r", stdin);
freopen ("2274.out", "w", stdout);
#endif
} const int N = 2e5 + 1e3, inf = 0x7f7f7f7f; int n, m, k, a, val[N], tag[N]; vector<int> seg[N]; priority_queue<int> P; inline bool check(int limit) {
int cnt = 0, add = 0, now, here;
while (!P.empty()) P.pop();
For (i, 1, n) tag[i] = 0;
For (i, 1, n) {
add += tag[i];
here = val[i] + add;
for(int j : seg[i]) P.push(j); while (here < limit) {
if (++ cnt > k || !(bool)P.size()) return false;
now = P.top(); P.pop();
if (now < i) return false;
tag[now + 1] -= a; here += a; add += a;
}
}
return true;
} int main() { File(); int cases = read();
while (cases--) {
int minv = inf;
n = read(); m = read(); k = read(); a = read();
For (i, 1, n)
chkmin (minv, val[i] = read()); For (i, 1, m) {
int pos = read();
seg[pos].push_back(read());
} int l = 1, r = minv + a * k, ans = 0;
while (l <= r) {
int mid = (l + r) >> 1;
if (check(mid)) l = mid + 1, ans = mid;
else r = mid - 1;
}
printf ("%d\n", ans);
For (i, 1, n) seg[i].clear();
} return 0; }

「JXOI2017」颜色

题意

有一个长度为 \(n\) 的颜色序列 \(A_i\) ,选择一些颜色把这些颜色的所有位置都删去。

删除颜色 \(i\) 可以定义为把所有满足 \(A_j = i\) 的位置 \(j\) 都从序列中删去。

想要知道有多少种删去颜色的方案使得最后剩下来的序列非空且连续。

\(n \le 3 \times 10^5\)

题解

认真读题,注意是 一起 删除。

考虑删除方案显然不太好算的,可以考虑最后剩下的序列。

我们记 \(\min_i\) 为 \(i\) 这种颜色最早出现的位置,\(\max_i\) 为 \(i\) 这种颜色最晚出现的位置。

枚举最后剩下序列的右端点 \(r\) ,我们只需要查找左端点 \(l\) 在哪里合法。

  1. 对于一种颜色 \(k\) ,如果存在 \(\max_k > r\) ,那么这种颜色 \(k\) 不能存在于 \([l, r]\) 之中。我们只需要找出 \(\max_{j < r} j\) 满足 \(\max_{A_j} > r\) ,那么 \(l \in (j, r]\) 。
  2. 对于一种颜色 \(k\) ,如果存在 \(\max_k \le r\) ,那么 \(l \notin (\min_k,\max_k]\) 。

不难发现只要满足这两个限制,外面的颜色可以一起删完且不会影响到中间这段区间的点。

第一个保证右端点向右合法,第二个保证左端点向左合法。

如何维护呢?

  1. 对于第一个求 \(j\) 直接维护一个 \(i\) 递增 \(\max_{A_i}\) 递减的单调栈就行了。
  2. 第二个直接到 \(max_{A_i}\) 的时候,在线段树上打一个 $ (\min_k,\max_k]$ 区间不可行的标记就行。

然后每次就直接线段树上查找可行节点个数就能轻松做完了。

代码

#include <bits/stdc++.h>
#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
using namespace std; inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;}
inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;} inline int read() {
int x = 0, fh = 1; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1;
for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
return x * fh;
} void File() {
#ifdef zjp_shadow
freopen ("2275.in", "r", stdin);
freopen ("2275.out", "w", stdout);
#endif
} const int N = 3e5 + 1e3;
struct Stack {
int pos[N], val[N], top; void Clear() { top = 0; } inline void Push(int p, int v) { pos[++ top] = p; val[top] = v; } inline int Max_Pos() { return pos[top]; } inline void Pop(int p) { while (top && val[top] <= p) -- top; }
} S; #define lson o << 1, l, mid
#define rson o << 1 | 1, mid + 1, r
struct Segment_Tree {
int sumv[N << 2]; bitset<(N << 2)> Tag; Segment_Tree () { Tag.reset(); } inline void push_down(int o) {
if (!Tag[o]) return ; sumv[o << 1] = sumv[o << 1 | 1] = 0; Tag[o << 1] = Tag[o << 1 | 1] = true; Tag[o] = false;
} inline void push_up(int o) { sumv[o] = sumv[o << 1] + sumv[o << 1 | 1]; } void Build(int o, int l, int r) {
Tag[o] = false; sumv[o] = r - l + 1; if (l == r) return ;
int mid = (l + r) >> 1; Build(lson); Build(rson);
} void Update(int o, int l, int r, int ul, int ur) {
if (!sumv[o] || ul > ur) return ;
if (ul <= l && r <= ur) { sumv[o] = 0; Tag[o] = true; return ; }
push_down(o); int mid = (l + r) >> 1;
if (ul <= mid) Update(lson, ul, ur); if (ur > mid) Update(rson, ul, ur); push_up(o);
} int Query(int o, int l, int r, int ql, int qr) {
if (!sumv[o] || ql > qr) return 0;
if (ql <= l && r <= qr) return sumv[o];
int res = 0, mid = (l + r) >> 1; push_down(o);
if (ql <= mid) res += Query(lson, ql, qr); if (qr > mid) res += Query(rson, ql, qr); return res; push_up(o);
}
} T; const int inf = 0x7f7f7f7f;
int n, Col[N], Min[N], Max[N]; int main () {
File();
int cases = read();
while (cases --) {
n = read();
For (i, 1, n) Col[i] = read(), Min[Col[i]] = inf, Max[Col[i]] = -inf;
For (i, 1, n) chkmax(Max[Col[i]], i), chkmin(Min[Col[i]], i); T.Build(1, 1, n); S.Clear();
long long ans = 0;
For (i, 1, n) {
S.Push(i, Max[Col[i]]); S.Pop(i); int Pos = S.Max_Pos();
if (i == Max[Col[i]]) T.Update(1, 1, n, Min[Col[i]] + 1, Max[Col[i]]);
ans += T.Query(1, 1, n, Pos + 1, i);
}
printf ("%lld\n", ans);
}
return 0;
}

总结

总的来说,这套吉老师出的题水平很高,做起来十分的舒服。

据说现场有大样例,但是没人看到。。。

如果给 \(5h\) 就很舒服,因为三题都需要对拍比较好。。。但是据说现场只有 \(3.5h\) 喵喵喵?

第一题考察了比较基础的计数 \(dp\) 和对性质的观察。

第二题考察了二分答案的基本应用然后转化为贪心选择区间覆盖的经典问题,用堆维护即可。

第三题依旧考察了对于性质的观察以及用数据结构维护可行节点的经典操作。

整体来说,是套好题。

JXOI 2017 简要题解的更多相关文章

  1. JXOI 2018 简要题解

    目录 「JXOI2018」游戏 题意 题解 代码 「JXOI2018」守卫 题意 题解 代码 「JXOI2018」排序问题 题意 题解 代码 总结 「JXOI2018」游戏 题意 可怜公司有 \(n\ ...

  2. codechef January Lunchtime 2017简要题解

    题目地址https://www.codechef.com/LTIME44 Nothing in Common 签到题,随便写个求暴力交集就行了 Sealing up 完全背包算出得到长度≥x的最小花费 ...

  3. codechef January Challenge 2017 简要题解

    https://www.codechef.com/JAN17 Cats and Dogs 签到题 #include<cstdio> int min(int a,int b){return ...

  4. JXOI 2017 颜色 题解

    T3 颜色 100/100 对于这题由于数据范围小,有一种神奇的做法,我们可以把每个值随机赋值,但是保证相同颜色的和为0,就代表消去了这个颜色,我们只要取寻找合法区间就行,合法区间的寻找只要找相同前缀 ...

  5. JXOI2018简要题解

    JXOI2018简要题解 T1 排序问题 题意 九条可怜是一个热爱思考的女孩子. 九条可怜最近正在研究各种排序的性质,她发现了一种很有趣的排序方法: Gobo sort ! Gobo sort 的算法 ...

  6. Noip 2014酱油记+简要题解

    好吧,day2T1把d默认为1也是醉了,现在只能期待数据弱然后怒卡一等线吧QAQ Day0 第一次下午出发啊真是不错,才2小时左右就到了233,在车上把sao和fate补掉就到了= = 然后到宾馆之后 ...

  7. Tsinghua 2018 DSA PA2简要题解

    反正没时间写,先把简要题解(嘴巴A题)都给他写了记录一下. upd:任务倒是完成了,我也自闭了. CST2018 2-1 Meteorites: 乘法版的石子合并,堆 + 高精度. 写起来有点烦貌似. ...

  8. Codeforces 863 简要题解

    文章目录 A题 B题 C题 D题 E题 F题 G题 传送门 简要题解?因为最后一题太毒不想写了所以其实是部分题解... A题 传送门 题意简述:给你一个数,问你能不能通过加前导000使其成为一个回文数 ...

  9. HNOI2018简要题解

    HNOI2018简要题解 D1T1 寻宝游戏 题意 某大学每年都会有一次 Mystery Hunt 的活动,玩家需要根据设置的线索解谜,找到宝藏的位置,前一年获胜的队伍可以获得这一年出题的机会. 作为 ...

随机推荐

  1. MySQL数据类型--日期和时间类型

    MySQL中的多种时间和格式数据类型 日期和时间类型是为了方便在数据库中存储日期和时间而设计的.MySQL中有多种表示日期和时间的数据类型. 其中,year类型表示时间,date类型表示日期,time ...

  2. iOS上手指点击波纹效果的实现

    https://www.jianshu.com/p/35e6f53ca0fe 2016.10.19 22:00* 字数 135 阅读 2468评论 2喜欢 7 闲暇时间做了一个反馈手指点击屏幕的效果, ...

  3. iOS QRcode识别及相册图片二维码读取识别

    https://www.jianshu.com/p/48e44fe67c1d 2016.03.30 10:32* 字数 892 阅读 16197评论 5喜欢 34赞赏 1 最近碰到一个用户 在使用我们 ...

  4. redis中的hash、列表、集合操作

    一.hash操作 数据结构:key:{k1:v1, k2:v2, k3:v3} 类似Python中的字典 如:info : {name: lina, age: 22, sex: F} hset key ...

  5. mysql-SQL Error: 1205, SQLState: 41000

    mysql-SQL Error: 1205, SQLState: 41000——CSDN问答频道https://ask.csdn.net/questions/176492 mysql-SQL Erro ...

  6. 【转】linux下查看磁盘分区的文件系统格式

    https://www.cnblogs.com/youbiyoufang/p/7607174.html

  7. Svn基本操作

    日常开发中使用到的Svn基本操作 svn      https://tortoisesvn.net/ https://www.visualsvn.com/server/download/   1. 检 ...

  8. C\C++学习笔记 1

    C++记录1 C的头文件为math.h C++的为 cmath using编译指令 namespace 区分不同产品的函数.Mics::cout Linux::cout cout << 即 ...

  9. Bootstrap知识记录:排版样式

    ---恢复内容开始--- 一.页面排版Bootstrap 提供了一些常规设计好的页面排版的样式供开发者使用.1.页面主体Bootstrap 将全局font-size 设置为14px,line-heig ...

  10. 解决 linux 下面解压缩 中文文件名乱码问题的方法 unzip -O CP936

    Linux 解压缩 zip包中文目录出现乱码的问题. 出现问题如图示: unzip -O CP936 xxx.zip 用这种方式处理一下就好了.