「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. Form的is_valid校验规则及验证顺序

    一.验证顺序   查看form下的源码了解顺序 BaseForm为基类,中间包含了is_valid校验方法 @html_safe class BaseForm: ......... self.is_b ...

  2. Telnet服务器和客户端请求处理

    Telnet服务器和客户端请求处理 本文的控制台项目是根据SuperSocket官方Telnet示例代码进行调试的,官方示例代码:Telnet示例. 开始我的第一个Telnet控制台项目之旅: 创建控 ...

  3. nodejs 中的一些方法

    fs.unlink(path, [callback(err)]) //删除文件操作. //path 文件路径 //callback 回调,传递一个异常参数err. ndoe中解决跨域问题 expres ...

  4. Nginx负载均衡各种配置方式

    Nginx负载均衡 - 小刚qq - 博客园http://www.cnblogs.com/xiaogangqq123/archive/2011/03/04/1971002.html Module ng ...

  5. vue上传图片

    在用这块代码前需要在主页面index引入<script src="http://at.alicdn.com/t/font_kfbh2nlnqrrudi.js">< ...

  6. HashMap深度解析(转载)

    原文地址:http://blog.csdn.net/ghsau/article/details/16890151 实现原理:用一个数组来存储元素,但是这个数组存储的不是基本数据类型.HashMap实现 ...

  7. 05 Hadoop 设置块的大小

    1.是在hdfs的配置文件中配置 2.是在app程序中设置 注意:假设配置文件的最大是   20K   最小是 10K   文件大小为72  块数就是 4 在程序中设置最大为15K    切割块数  ...

  8. Springboot自定义过滤器Filter

    前言:自己写了个Springboot项目,最近写的功能越来越多,结合业务已经要写过滤器Filter来过滤处理一些请求. 在网上看了几篇博客,总结如下: 过滤器配置方式有两种: 1.通过@WebFilt ...

  9. SQL查询临时表空间的数据

  10. java学习之—递归实现变位字

    /** * 递归实现变位字 * Create by Administrator * 2018/6/20 0020 * 上午 10:23 **/ public class AnagramApp { st ...