JXOI 2018 简要题解
「JXOI2018」游戏
题意
可怜公司有 \(n\) 个办公室,办公室编号是 \(l\sim l+n-1\) ,可怜会事先制定一个顺序,按照这个顺序依次检查办公室。一开始的时候,所有办公室的员工都在偷懒,当她检查完编号是 \(i\) 的办公室时候,这个办公室的员工会认真工作,并且这个办公室的员工通知所有办公室编号是 \(i\) 的倍数的办公室,通知他们老板来了,让他们认真工作。因此,可怜检查完第 \(i\) 个办公室的时候,所有编号是 \(i\) 的倍数(包括 \(i\) )的办公室的员工会认真工作。
她发现,对于每种不同的顺序 \(p\) ,都存在一个最小的 \(t(p)\) ,使得可怜按照这个顺序检查完前 \(t(p)\) 个办公室之后,所有的办公室都会开始认真工作。她把这个 \(t(p)\) 定义为 \(p\) 的检查时间。
可怜想知道所有 \(t(p)\) 的和对 \(10^9+7\) 取模后的结果。
\(l, n \le 10^7\)
题解
比较舒服的签到题。
我们定义 \([l, r]\) 中的神仙数 \(p\) ,当且仅当 \(p\) 除了 \(p\) 外的任意一个因子都不存在于 \([l, r]\) 中。
这个显然我们可以用线性筛预处理,做到 \(O(n)\) 的复杂度。其实也可以通过埃筛做到 \(O(n \ln \ln n)\) 的复杂度。
假设 \([l, r]\) 中的神仙数共有 \(tot\) 个,那么就意味着只要这 \(tot\) 个数全部遍历过就可以结束了,反之不能结束。
这是很好证明的,因为这些数不遍历的话,那么不存在别的数能把他们消掉。
反之这些数遍历了,别的数都能通过这些数消掉。
那么枚举在第几个数遍历完,那么贡献就很好计算了:
\]
假设在 \(i\) 处结束,那么第 \(i\) 个必为神仙数,那么就是在前 \(i - 1\) 个位置填 \(tot - 1\) 的排列,然后其他数可以随意安排,注意不要漏乘此处的贡献是 \(i\) 。
然后线性预处理逆元就可以把复杂度做到 \(O(n)\) 了。
代码
#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;
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 ("2544.in", "r", stdin);
freopen ("2544.out", "w", stdout);
#endif
}
typedef long long ll;
const int N = 1e7 + 1e3, Mod = 1e9 + 7;
ll fpm(ll x, int power) {
ll res = 1;
for (; power; power >>= 1, (x *= x) %= Mod)
if (power & 1) (res *= x) %= Mod;
return res;
}
int l, r, n; ll fac[N], ifac[N];
inline int C(int n, int m) {
if (n < 0 || m < 0 || m > n) return 0;
return 1ll * fac[n] * ifac[m] % Mod * ifac[n - m] % Mod;
}
void Init(int maxn) {
fac[0] = ifac[0] = 1;
For (i, 1, maxn) fac[i] = 1ll * fac[i - 1] * i % Mod;
ifac[maxn] = fpm(fac[maxn], Mod - 2);
Fordown (i, maxn - 1, 1)
ifac[i] = 1ll * ifac[i + 1] * (i + 1) % Mod;
}
bitset<N> vis;
int main () {
File();
l = read(); r = read(); n = r - l + 1;
Init(1e7);
int tot = 0, ans = 0;
For (i, l, r) if (!vis[i]) {
++ tot; for (int j = i; j <= r; j += i) vis[j] = true;
}
For (i, tot, n)
(ans += 1ll * C(i - 1, tot - 1) * fac[tot] % Mod * fac[n - tot] % Mod * i % Mod) %= Mod;
cout << ans << endl;
return 0;
}
「JXOI2018」守卫
题意
九条可怜是一个热爱运动的女孩子。这一天她去爬山,她的父亲为了她的安全,雇了一些保镖,让他们固定地呆在在山的某些位置,来实时监视九条可怜,从而保护她。
具体来说,一座山可以描述为一条折线,折线的下方是岩石。这条折线有 \(n\) 个折点,每个折点上有一个亭子,第 \(i\) 个折点的坐标是 \((i,h_i)\) 。九条可怜只可能会在亭子处玩耍,那些保镖也只会在亭子处监视可怜。
由于技术方面的原因,一个保镖只能监视所有他能看得到的,横坐标不超过他所在位置的亭子。我们称一个保镖能看到一个亭子 \(p\) ,当且仅当他所在的亭子 \(q\) 和 \(p\) 的连线不经过任何一块岩石。特别地,如果这条连线恰好经过了除了 \(p,q\) 以外的亭子,那么我们认为保镖看不到可怜。
他想对所有的 \(1\leq l\leq r\leq n\) 计算:如果事先已知了只有区间 \([l,r]\) 的亭子可以用来玩耍(和监视),那么最少需要多少个保镖,才能让 \([l,r]\) 中的每一个亭子都被监视到。
对于 \(100\%\) 的数据, \(n\leq 5000, 1\leq h_i\leq 10^9\) 。
题解
参考了 Ebola 大佬的博客 。
其实自己想了一下,类似于贪心的思路,看起来很对,但是没写。。参考了一下别人的做法,很优秀嘛。
由于每个点只能看它左边的节点,我们可以枚举区间 \([l,r]\) 的右端点 \(r\) ,因为 \(r\) 是必定选择的,然后考虑从右向左枚举 \(l\) ,记下当前 \(r\) 能看到的在 \(l\) 右边的第一个点 \(pos\) 。
我们记 \(sum\) 为 \([pos, r]\) 最少需要安放保镖的数量,\(dp_{l, r}\) 为 \([l, r]\) 这段区间所需要的最少保镖数量。
所以会需要在 \(pos\) 或者 \(pos - 1\) 的位置安放一个保镖才能看到 \(l\) ,然后转移就是
\]
每次到一个新的 \(r\) 可以看到的节点 \(l\) 的时候,我们需要看到 \([l + 1, pos - 1]\) 之间的所有节点,所以在 \(pos\) 或者 \(pos - 1\) 处安放一个保镖,所以 \(sum\) 加上 \(\min(dp_{l + 1, pos - 1}, dp_{l + 1, pos})\) ,将 \(pos\) 更新到 \(l\) 即可。
然后,判断是否可以看到,可以用叉积判断。我懒,用斜率算了。。反正也可以过。。
代码
#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 ("2545.in", "r", stdin);
freopen ("2545.out", "w", stdout);
#endif
}
const int N = 5010;
int n, h[N], dp[N][N], ans = 0;
inline double Slope(int a, int b) {
return (h[b] - h[a]) / (b - a);
}
inline bool Pass(int x, int a, int b) {
return Slope(x, b) < Slope(x, a);
}
int main() {
File();
n = read();
For (i, 1, n) h[i] = read();
For (r, 1, n) {
int sum = 1, pos = 0;
ans ^= (dp[r][r] = 1);
Fordown (l, r - 1, 1) {
if (!pos || Pass(r, pos, l))
sum += min(dp[l + 1][pos - 1], dp[l + 1][pos]), pos = l;
ans ^= (dp[l][r] = sum + min(dp[l][pos - 1], dp[l][pos]));
}
}
printf ("%d\n", ans);
return 0;
}
「JXOI2018」排序问题
题意
九条可怜是一个热爱思考的女孩子。
Gobo sort
是把序列 \(A\) 不断的执行 std :: random_shuffle
直到序列 \(A\) 单调不下降的一种奇怪的排序方式。
于是她就想到了这么一个问题:
现在有一个长度为 \(n\) 的序列 \(x\) ,九条可怜会在这个序列后面加入 \(m\) 个元素,每个元素是 \([l,r]\) 内的正整数。
她希望新的长度为 \(n+m\) 的序列执行 Gobo sort
的期望执行轮数尽量的多。
她希望得到这个最多的期望轮数对 \(998244353\) 取模的结果。
对于 \(100\%\) 的数据, \(T\leq 10^5,n\leq 2\times 10^5,m\leq 10^7,1\leq l\leq r\leq 10^9, 1\leq a_i\leq 10^9,\sum{n}\leq 2\times 10^6\) 。
题解
根据样例可以猜出,期望的次数为单次成功概率 \(p\) 的倒数,至于证明,可以参考我 这篇博客 。
那么意味着 ,\(A\) 总共能构成的本质不同的序列个数就是期望的次数。
然后对于一个可重排列的重组个数是很容易计算的。
假设共有 \(n\) 种数字,其中第 \(i\) 种出现的次数为 \(p_i\) ,那么个数就为
\]
就是总排列个数除掉这些数字对应任意排列的排列个数。
那么不难想到一个贪心的做法,每次从 \([l, r]\) 区间内选择一个出现次数 \(p_x\) 最少的元素 \(x\) ,当前加入的元素为 \(x\) 。
不难发现这样是正确的,因为我们是需要最大化序列个数,那么意味着每次多除掉的那个的那个元素要尽量小,也就是从 \(p_x \to p_x + 1\) ,多除的那个 \(p_x + 1\) 要尽量小。
然后直接这样做是 \(\displaystyle O(\sum_{i = 1}^{T} m_i(r_i - l_i))\) 的,显然通过不了。
由于 \(T \times m\) 与 \(r - l\) 都比较大,所以复杂度不能是 \(O(Tm)\) 或者 \(O(\sum r - l)\) 。
其实也很容易解决这个问题,只需要按出现次数从小到大枚举 \([l, r]\) 在原来序列 \(A\) 出现的元素 \(p\) 。
然后每次考虑能否把次数 \(< p\) 的元素用 \(m\) 次操作全部均匀的填上来,最多分成两堆出现次数一样的元素,一堆为 \(x\) 令一堆为 \(x+1\) ,因为这些元素贡献一样直接除去 \(x!, x+1!\) 的快速幂即可。
至于细节可以自行摸索,本文提供了一个实现的代码。
最后复杂度就是 \(O(\sum n \log n+ \max (m + n) + T \log m)\) 的,需要卡常然后可以通过。
注意实现的时候有几个细节。不能用 std :: map<int, int> or std :: unordered_map<int, int>
通通被卡常,只能手动 sort
来离散化。
代码
因为 \(18MB\) 的读入,蒯了个读入优化上来。。。
#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__)
#define pb push_back
using namespace std;
typedef long long ll;
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;}
namespace pb_ds
{
namespace io
{
const int MaxBuff = 1 << 15;
const int Output = 1 << 25;
char B[MaxBuff], *S = B, *T = B;
#define getc() ((S == T) && (T = (S = B) + fread(B, 1, MaxBuff, stdin), S == T) ? 0 : *S++)
char Out[Output], *iter = Out;
inline void flush()
{
fwrite(Out, 1, iter - Out, stdout);
iter = Out;
}
}
template<class Type> inline Type read()
{
using namespace io;
register char ch; register Type ans = 0; register bool neg = 0;
while(ch = getc(), (ch < '0' || ch > '9') && ch != '-') ;
ch == '-' ? neg = 1 : ans = ch - '0';
while(ch = getc(), '0' <= ch && ch <= '9') ans = ans * 10 + ch - '0';
return neg ? -ans : ans;
}
}
using namespace pb_ds;
void File() {
#ifdef zjp_shadow
freopen ("2543.in", "r", stdin);
freopen ("2543.out", "w", stdout);
#endif
}
const int N = 2e7 + 1e3, Mod = 998244353;
int fac[N], ifac[N];
inline int fpm(int x, int power) {
int res = 1;
for (; power; power >>= 1, x = 1ll * x * x % Mod)
if (power & 1) res = 1ll * res * x % Mod;
return res;
}
void Math_Init(int maxn) {
fac[0] = ifac[0] = 1;
For (i, 1, maxn) fac[i] = 1ll * fac[i - 1] * i % Mod;
ifac[maxn] = fpm(fac[maxn], Mod - 2);
Fordown (i, maxn - 1, 1) ifac[i] = 1ll * ifac[i + 1] * (i + 1) % Mod;
}
int n, m, l, r, a[N];
vector<int> V;
int main() {
File();
Math_Init(2e7);
for (int cases = read<int>(); cases; -- cases) {
n = read<int>(); m = read<int>(); l = read<int>(); r = read<int>(); V.clear();
For (i, 1, n) a[i] = read<int>();
int ans = fac[n + m];
sort(a + 1, a + n + 1);
for (int i = 1, pos = 1; i <= n; pos = i = pos + 1) {
while (pos < n && a[pos + 1] == a[i]) ++ pos;
if (a[i] < l || a[i] > r || !m)
ans = 1ll * ans * ifac[pos - i + 1] % Mod;
else V.pb(pos - i + 1);
}
if (!m) { printf ("%d\n", ans); continue ; }
sort(V.begin(), V.end());
ll tot = (r - l + 1) - (int)(V.size()), sum = 0, pre = 0;
V.pb(1e9);
For (i, 0, V.size() - 1) {
sum += tot * (V[i] - (i ? V[i - 1] : 0));
if (sum >= m) {
m += pre; ll rest = m % tot, up = tot - rest;
ans = 1ll * ans * fpm(ifac[m / tot], up) % Mod * fpm(ifac[m / tot + 1], rest) % Mod;
For(j, i, V.size() - 2) ans = 1ll * ans * ifac[V[j]] % Mod; break ;
}
pre += V[i]; ++ tot;
}
printf ("%d\n", ans);
}
#ifdef zjp_shadow
cerr << (double) clock() / CLOCKS_PER_SEC << endl;
#endif
return 0;
}
总结
吉老师出的这套题整体难度适中,但不失为具有区分度的一套好题。
第一题送签到,但也考察了数论的基础、对于性质的观察以及组合数学的基础知识。
第二题略有难度,但认真观察,还是能推出 \(dp\) ,\(O(n ^2)\) 给了很多不同的做法来选择,贪心或许也是一个可以的思路?
第三题略微处理细节,整体思路还是较为容易的,出题人没有设较大的难点。
总体来说,仍然需要提高细节处理能力,以及拥有及时调整开题顺序的想法及手段。
JXOI 2018 简要题解的更多相关文章
- JXOI 2017 简要题解
「JXOI2017」数列 题意 九条可怜手上有一个长度为 \(n\) 的整数数列 \(r_i\) ,她现在想要构造一个长度为 \(n\) 的,满足如下条件的整数数列 \(A\) : \(1\leq A ...
- NOIP 2018 简要题解
从这里开始 Day 1 Problem A 考虑贪心地选取极大非 0 段减少. 如果两次操作有交,并且不是包含关系,那么把其中一次操作的,但另一次没有操作的移过去,然后就变成了上面那个贪心了. Cod ...
- codechef February Challenge 2018 简要题解
比赛链接:https://www.codechef.com/FEB18,题面和提交记录是公开的,这里就不再贴了 Chef And His Characters 模拟题 Chef And The Pat ...
- HNOI 2018 简要题解
寻宝游戏 毒瘤题. 估计考试只会前30pts30pts30pts暴力然后果断走人. 正解是考虑到一个数&1\&1&1和∣0|0∣0都没有变化,&0\&0& ...
- Tsinghua 2018 DSA PA2简要题解
反正没时间写,先把简要题解(嘴巴A题)都给他写了记录一下. upd:任务倒是完成了,我也自闭了. CST2018 2-1 Meteorites: 乘法版的石子合并,堆 + 高精度. 写起来有点烦貌似. ...
- JXOI2018简要题解
JXOI2018简要题解 T1 排序问题 题意 九条可怜是一个热爱思考的女孩子. 九条可怜最近正在研究各种排序的性质,她发现了一种很有趣的排序方法: Gobo sort ! Gobo sort 的算法 ...
- A · F · O —— JLOI2018翻车记(附Day1简要题解)
JLOI2018翻车记 并不知道该怎么写... 算了还是按照标准剧情来吧 这应该是一篇写得非常差的流水账... 2018.04.04 Day -1 省选前在机房的最后一天. 压力并不是很大,毕竟联赛 ...
- Noip 2014酱油记+简要题解
好吧,day2T1把d默认为1也是醉了,现在只能期待数据弱然后怒卡一等线吧QAQ Day0 第一次下午出发啊真是不错,才2小时左右就到了233,在车上把sao和fate补掉就到了= = 然后到宾馆之后 ...
- Codeforces 863 简要题解
文章目录 A题 B题 C题 D题 E题 F题 G题 传送门 简要题解?因为最后一题太毒不想写了所以其实是部分题解... A题 传送门 题意简述:给你一个数,问你能不能通过加前导000使其成为一个回文数 ...
随机推荐
- OSS网页上传和断点续传(STSToken篇)
云账号AccessKey拥有所有API访问权限,在客户端不要直接使用,会泄露ak信息,造成安全问题.所以使用STS方式(临时账号权限)给客户端授权. C#版获取STSToken 一.下载阿里SDK(a ...
- Array Queries CodeForces - 797E
题目链接 非常好的一道题目, 分析,如果用暴力的话,时间复杂度是O(q*n)稳稳的超时 如果用二维DP的话,需要O (n*n)的空间复杂度,会爆空间. 那么分析一下,如果k>sqrt(n)的话, ...
- nginx配置ssl证书后无法访问https
一直听说https更安全,要安装证书,一直没试过,今天终于试了试 首先得有个http的域名网站,服务器. 到阿里云的安全-ssl证书管理申请一个免费的,可以绑定一个域名 然后完善资料,照着例子配置一 ...
- CentOS7 下面安装jdk1.8
1. 卸载已有的jdk rpm -qa |grep jdk |xargs rpm -e --nodeps 2. 使用xftp上传 jdk 的文件我这里上传的是 jdk-8u121-linux-x64. ...
- [转帖]SPU、SKU、ID,它们都是什么意思,三者又有什么区别和联系呢?
SPU.SKU.ID,它们都是什么意思,三者又有什么区别和联系呢? http://blog.sina.com.cn/s/blog_5ff11b130102wx0p.html 电商时代,数据为王. 所以 ...
- [转帖]50个必知的Linux命令技巧,你都掌握了吗?
50个必知的Linux命令技巧,你都掌握了吗? https://blog.51cto.com/lizhenliang/2131141 https://blog.51cto.com/lizhenlian ...
- 模态框 modal data-toggle data-target
模态框 modal data-toggle data-target 1. Data-*属性 模态框(modal) 触发事件(data-toggle) 触发对象data-target(ID 或类) ...
- java随笔5 完整路径的应用
不仅类,函数,甚至参数都可以获取完整路径
- outline,box-shadow,border-radius小例子
<!doctype html> <html> <head> <meta charset="utf-8"> <title> ...
- PHP涉及到的英文单调
slashes [slæʃeis]:斜线 uppercase ['ʌpəˌkeɪs]:大写字母,简写uc strip [strɪp]:去掉 trim [trɪm]:整理(修剪) explode [ɪk ...