「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. Of Study

    Bacon Reading maketh a full man; conference a ready man; and writing an exact man. And therefore, if ...

  2. java总结:double取两位小数的多种方法

    1.方法一 四舍五入: import java.math.BigDecimal; double f = 111231.5585; BigDecimal b = new BigDecimal(f); d ...

  3. Es5中的类和静态方法 继承

    Es5中的类和静态方法 继承(原型链继承.对象冒充继承.原型链+对象冒充组合继承) // es5里面的类 //1.最简单的类 // function Person(){ // this.name='张 ...

  4. centos6 yum 安装memcached

    centos6 yum 安装memcached - 像块石头 - 博客园http://www.cnblogs.com/rockee/archive/2012/08/01/2619160.html yu ...

  5. centos7 network eno16777736

    Network service not running - eno16777736 not activated - CentOShttps://www.centos.org/forums/viewto ...

  6. Ubuntu16系统中安装htpasswd

    htpasswd是Apache附带的程序, htpasswd生成包含用户名和密码的文本文件, 每行内容格式为“用户名:密码”, 用于用户文件的基本身份认证. 当用户浏览某些网页的时候, 浏览器会提示输 ...

  7. php之IP

    常用的获取客户端的IP地址的方法: 1) function getRemoteIp(){ if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])){ $ip = $_SE ...

  8. css小demo

    span{ color: #ccc; float: right; font-weight: bold; display: inline-block; border-right: solid 1px # ...

  9. laravel 循环中子元素使用&符号嵌入到父级,经典版

    /**ajax 获取企业名称 * * @param Request $request * * @return \Illuminate\Http\JsonResponse * @author lxw * ...

  10. laravel实现批量添加数据

    在使用laravel eloquent进行数据库操作的时候惊讶的发现这货居然不支持批量添加,看到网上很多人在循环里进行数据库插入操作来实现批量添加,我想说这样做是很损失性能滴!好在框架的DB门面里的i ...