「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. (Beta)Let's-M2后分析报告

    设想和目标 1. 我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? 在M1阶段我们对用户需求进行了调研,同时M1阶段我们的开发目标就是为了解决用户发起.参与.查看.搜 ...

  2. NFV论文集(二)

    一 文章名称:VNF Placement with Replication for Load Balancing in NFV Networks 发表时间:2017 期刊来源:ICC: IEEE In ...

  3. scrapy框架爬取妹子图片

    首先,建立一个项目#可在github账户下载完整代码:https://github.com/connordb/scrapy-jiandan2 scrapy startproject jiandan2 ...

  4. MongoDB Redis

    MongoDB Redis设置用户名密码了吗?看看shodan这款邪恶的搜索引擎吧!~   早上看新闻的时候看到了个醒目的新闻 开源中国:MongoDB 赎金事件持续发酵,究竟是谁之过?博客园:Mon ...

  5. HashSet中存放不重复元素

    一.自定义对象存放在hashSet中,保证元素不重复.重写hashCode()和equals()方法 public class Student{ private String name; privat ...

  6. Eclipse lombok java

    Stablehttps://projectlombok.org/features/all Lombok介绍及使用方法 - holten - 博客园http://www.cnblogs.com/holt ...

  7. windows下linux子系统安装

    1.打开Windows功能中的使用于linux的Windows子系统 2.应用商店中下载需要的linux 3.下载完成后运行等待安装并输入用户名密码  4.查看系统信息 先后 sudo apt-get ...

  8. VS2008引入头文件包含目录和lib库目录

    全局级别的引入 为VS所有项目设置包含目录和库目录,对所有项目都有效 如下图所示:工具-选项-项目和解决方案-VC++目录-包含文件:在此添加头文件目录即可 工具-选项-项目和解决方案-VC++目录- ...

  9. Javascript与C#对变量的处理方式

      先来看一下Javascript的情况(下面所说的基本类型和简单类型是一个意思): Javascript中变量会存在两种情况,一种是基本类型的,一共有五种,有null.Bollean.undefin ...

  10. 在Laravel中使用数据库事务以及捕获事务失败后的异常

    Description 在Laravel中要想在数据库事务中运行一组操作,则可以在 DB facade 中使用 transaction 方法.如果在事务的闭包内抛出异常,事务将会被自动还原.如果闭包运 ...