写在前面的总结

离联赛只有几天了,也马上就要回归文化课了。

有点舍不得,感觉自己的水平刚刚有点起色,却又要被抓回文化课教室了,真想在机房再赖几天啊。

像19/11/11那场的简单题,自己还是能敲出一些比较稳的暴力,虽然不见得能拿很高档的暴力或者打出正解,但至少不会挂分,最后能拿到的分数也还能看。但是一上点难度,或者不对胃口,就明显感觉力不从心。总是只能打最低档的暴力,甚至有些题只能拿\(10pts\)的\(dfs\)分。有优化想不出来,有式子也推不出来。时间也总是不够用——在某道题上浪费了太多时间,最后刚刚推出一点接近正解(或者更高分的暴力)的结论就要考试结束了。

真是菜的令人发指……= =

讲实话,我从来不是什么很强的选手,甚至可能就中下而已。每场考试也不奢望能想到什么正解,只求能多拿一点分是一点分,把自己能想到的暴力稳稳当当地敲出来,不要犯什么低级错误就谢天谢地(其实以我的水平,也没有什么资本犯低级错误吧(苦笑),运气好的话,能推出一些特殊性质的部分分(然后再超常发挥地把它们敲出来?),不求考得多好,只求不要挂太难看……

最后这么几天攻难题似乎也没什么太大意义,把任务计划清理一下,然后打一打之前一直不熟悉的模板。一直逃避的高精,死活想不到状态的状压,因为已经死了所以没怎么用过的SPFA,没在考场上打过的主席树和平衡树,还有因为考的不多根本没怎么写过的字符串算法……至于剩下的,也只能顺其自然了。


懒得放题面了,自己找\(pdf\)吧

T1

我们知道\(string\)的排序是按字典序来比较的,这决定了在一堆字符串排完序之后两个字符串越接近,公共前缀长度就越长(即他们越相似),那么直接排完序之后每次抓一个不在自己位置上的,和它该在的位置上的字符串交换一下并记录方案即可

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#pragma GCC optimize(2)
#define N (1000000 + 10)
using namespace std;
int n, cnt;
int rk[N];
string s[N];
int pos[N];
struct node2 {
int x, y;
}ope[N];
inline bool cmp (int a, int b) { return s[a] < s[b];}
int main() {
scanf("%d", &n);
for (register int i = 1; i <= n; ++i) cin >> s[i], pos[i] = i;
sort (pos + 1, pos + n + 1, cmp);
for (register int i = 1; i <= n; ++i) rk[pos[i]] = i;
for (register int i = 1; i <= n; ++i) {
if (pos[i] != i) {
ope[++cnt].x = pos[i], ope[cnt].y = i, swap(rk[pos[i]], rk[i]), swap(pos[rk[pos[i]]], pos[i]);
}
}
cout << cnt << '\n';
for (register int i = 1; i <= cnt; ++i) cout << ope[i].x << " " << ope[i].y << '\n';
return 0;
}

T2

二分+主席树验证

此题的\(trick\)有学习意义,\(kma\)太菜了并没有见过

暴力分可以贪心,发现显然只有最大的一个对答案有影响,所以每次抓最大的来减即可

正解

二分一下最终答案\(len\),每秒减少的值并不是问题,在\(check\)的时候把这个量加上去就行

考虑\(check\)在\(s\)次以内能不能把所有小麦值操作到\(len\)以下

首先我们需要\(check\)的只有\(a_i > len\)的,因为剩下的对答案没影响

那么我们最终的操作次数\(times = \sum\limits_{a_i > len}\lceil\frac{a_i - len}{x}\rceil\)

发现这个式子并不好维护到单次查询\(O(logn)\)的复杂度,也不能直接强拆,考虑巧妙地拆开分别维护

定义\({a}\)表示实数\(a\)的小数部分

将上式变形得到

\[\begin{aligned}times &= \sum\limits_{a_i > len}\lceil\lfloor{\frac{a_i}{x}\rfloor + \{\frac{a_i}{x}\} - \lfloor\frac{len}{x}\rfloor-\{\frac{len}{x}\}}\rceil \\ &= \sum\limits_{a_i > len}\lceil(\lfloor{\frac{a_i}{x}\rfloor - \lfloor\frac{len}{x}\rfloor ) + (\{\frac{a_i}{x}\} - \{\frac{len}{x}\})}\rceil \\ &= \sum\limits_{a_i > len}\{(\lfloor{\frac{a_i}{x}\rfloor - \lfloor\frac{len}{x}\rfloor ) + \lceil (\{\frac{a_i}{x}\} - \{\frac{len}{x}\})}\rceil\} \\ &= \sum\limits_{a_i > len}\{(\lfloor{\frac{a_i}{x}\rfloor - \lfloor\frac{len}{x}\rfloor ) + [(a_i\ mod\ x) > (len\ mod\ x)}]\} \end{aligned}
\]

对于每一坨,我们看看都能怎么搞

\(\sum\limits_{a_i < len} \lfloor\frac{a_i}{x}\rfloor\)在\(a_i\)排序之后是一个后缀和的形式,预处理之后每次询问二分找一下这个点,\(O(logn)\)

\(\sum\limits_{a_i > len} \lfloor\frac{len}{x} \rfloor\)是一个常量乘上累加的个数,假设上面二分找到的端点是\(k\),那么它等价于\((n - k + 1) * \lfloor\frac{len}{x}\rfloor\),\(O(1)\)

\(\sum\limits_{a_i < len}[(a_i\ mod \ x) > (len\ mod\ x)]\),发现这实际上是一个后缀意义上的区间查找某一个数的\(rank\),对\(a_i\ mod\ x\)离散化一下建一棵主席树之后在上面查询\(len\ mod\ x\)的排名即可,\(O(logn)\)

容易发现对于每次二分的\(check\),我们做到了\(O(logn)\)的复杂度,再加上前面的排序离散化之类的预处理,总复杂度为\(O(nlogn + mlog^2n)\)

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read() {
ll cnt = 0, f = 1; char c = getchar();
while (!isdigit(c)) {if (c == '-') f = -f; c = getchar();}
while (isdigit(c)) {cnt = (cnt << 3) + (cnt << 1) + (c ^ 48); c = getchar();}
return cnt * f;
}
const int N = (int)1e5 + 5;
int n, m, rt[N];
ll x, t[N], sum[N], b[N], sz, cnt, lim, s, nx;
void pre_work() {
sort (b + 1, b + sz + 1);
sz = unique(b + 1, b + sz + 1) - b - 1;
}
struct node {
int l, r;
int sum;
#define l(p) tree[p].l
#define r(p) tree[p].r
#define sum(p) tree[p].sum
}tree[N * 32];
inline int _copy (int u) {
int v = ++cnt;
l(v) = l(u), r(v) = r(u);
sum(v) = sum(u); return v;
}
void Insert(int &p, int l, int r, int pos) {
p = _copy(p); ++sum(p);
if (l == r) return;
int mid = (l + r) >> 1;
if (pos <= mid) Insert(l(p), l, mid, pos);
else Insert(r(p), mid + 1, r, pos);
}
int query(int pos, int l, int r, int x) {
if (!pos||l >= x) return sum(pos);
int mid = (l + r) >> 1;
if (x <= mid) return sum(r(pos)) + query(l(pos), l, mid, x);
return query(r(pos), mid + 1, r, x);
}
bool ck(ll len, ll s) { //check - s
ll tot = 0; len += s;
int k = upper_bound(t + 1, t + n + 1, len) - t;
if (k > n) return true;
tot = sum[k]; tot -= (n - k + 1) * (len / x);
len = lower_bound(b + 1, b + sz + 1, len % x + 1) - b;
// if (tot < len) return true;
if (len <= sz) tot += (ll)query(rt[k], 1, sz, len);
return tot <= s;
}
ll binary(ll l, ll r) {
while (l < r) {
ll mid = (l + r) >> 1;
if (ck(mid, s)) r = mid;
else l = mid + 1;
}
return l;
}
int main() {
// freopen("bone.in", "r", stdin);
// freopen("bone.out", "w", stdout);
n = read(), m = read(), x = read();
for (register int i = 1; i <= n; ++i) t[i] = read(), lim = max(t[i], lim);
for (register int i = 1; i <= n; ++i) b[++sz] = t[i] % x;
pre_work();
sort (t + 1, t + n + 1);
for (register int i = n; i >= 1; --i) sum[i] = t[i] / x + sum[i + 1];
for (register int i = n; i >= 1; --i) {
ll gx = t[i] % x;
rt[i] = rt[i + 1];
gx = lower_bound(b + 1, b + sz + 1, gx) - b;
Insert(rt[i], 1, sz, gx);
}
for (register int i = 1; i <= m; ++i) s = read(), printf("%lld\n", binary(0, lim - s));
return 0;
}

T3

组合计数

总共有\(n-1\)个位置可以填+

对于每个\(a_i\)的贡献分别计算,考虑\(a_i\)之后离它最近的+在哪里,因为这决定了\(a_i\)最后对于答案的贡献需要给\(a_i\)乘上一个\(base\)的多少次方(\(10^i\))

如果+在\(a_i\)之后, 那么还剩下\(n - 2\)个空位可以填,还剩下\(m - 1\)个+号,\(a_i\)的贡献是\(a_i * C_{n - 2}^{m - 1}\)

如果+在\(a_{i + 1}\)之后,那么还剩下\(n - 3\)个空位可以填,还剩下\(m - 1\)个+号,\(a_i\)的贡献是\(a_i * 10^{1} * C_{n - 3}^{m - 1}\)

\(\cdots\)

如果+在\(a_{i + n - m - 1}\)之后,那么还剩下\(m - 1\)个空位可以填,还剩下\(m - 1\)个+号,\(a_i\)的贡献是\(a_i * 10^{n - m + 1} * C_{m - 1}^{m - 1}\)

\(a_{i +n - m - 1}\)之后是能放的最后一个位置,因为如果再往后的话,其他+会有堆在一起或出现在开头结尾的情况(空位不够)

对于+在\(a_n\)之后的情况单独讨论,这种相当于\(a_i\)存在于最后一个整数中,那么还剩\(i - 1\)个空位可以填,\(m\)个+,此时\(a_i\)的贡献是\(a_i * 10^{n - i} * C_{i - 1}^m\)

全部求和,暴力的复杂度是\(O(n^2)\)

发现对于每个乘\(10^k\)的\(a_i\),它们要乘的\(10^k\)是一样的,并且它们是连续的,考虑前缀和优化到\(O(n)\)

最后的柿子是$$\sum\limits_{i = 1}^{n - m}10^i * (\sum\limits_{j = 1}^{n - i} a_j * C_{n - i - 1}^{m - 1} + a_{n - i + 1} * C_{n - i}^m)$$

其中\(\sum\limits_{j = 1}^{n - i}a_j\)需要处理一个前缀和

#include<bits/stdc++.h>
#define ll long long
#define int ll
using namespace std;
inline int read() {
int cnt = 0, f = 1; char c = getchar();
while (!isdigit(c)) {if (c == '-') f = -f; c = getchar();}
while (isdigit(c)) {cnt = (cnt << 3) + (cnt << 1) + (c ^ 48); c = getchar();}
return cnt * f;
}
const ll mod = 998244353;
const int N = (int)1e6 + 5;
int n, m;
char x;
ll sum[N], fac[N], inv[N], a[N], ans;
ll mul (ll a, ll b) {return (a % mod * (b % mod)) % mod;}
ll add (ll a, ll b) {return a + b > mod ? a + b - mod : a + b;}
ll qpow(ll a, ll b) {ll c = 1; for (; b; b >>= 1, a = mul(a, a)) if (b & 1) c = mul(c, a); return c;}
void pre_work() {
fac[0] = 1, fac[1] = 1;
for (register int i = 2; i <= n; ++i) fac[i] = mul(fac[i - 1], (ll)i); inv[n] = qpow(fac[n], mod - 2);
for (register int i = n - 1; i >= 1; --i) inv[i] = mul(inv[i + 1], i + 1); inv[0] = 1;
}
ll C(ll m, ll n) {return mul(mul(fac[n], inv[m]), inv[n - m]);} signed main() {
n = read(), m = read();
for (register int i = 1; i <= n; ++i) cin >> x, a[i] = (x ^ 48), sum[i] = add(a[i], sum[i - 1]);
pre_work();
for (register int i = 1; i <= n - m; ++i) {
ans = add(mul(qpow(10, i - 1), add(mul(sum[n - i], C(m - 1, n - i - 1)), mul(a[n - i + 1], C(m, n - i)))), ans);
}
printf("%lld", ans);
return 0;
}

2019/11/12 CSP模拟赛&&考前小总结的更多相关文章

  1. 【2019.8.12 慈溪模拟赛 T1】钥匙(key)(暴力DP)

    暴力\(DP\) 这题做法很多,有\(O(n^2)\)的,有\(O(n^2logn)\)的,还有徐教练的\(O(nlogn)\)的,甚至还有\(bzt\)的二分+线段树优化建图的费用流. 我懒了点,反 ...

  2. 【2019.8.12 慈溪模拟赛 T2】汪哥图(wang)(前缀和)

    森林 考虑到题目中给出条件两点间至多只有一条路径. 就可以发现,这是一个森林. 而森林有一个很有用的性质. 考虑对于一棵树,点数-边数=\(1\). 因此对于一个森林,点数-边数=连通块个数. 所以, ...

  3. 2019/11/8 CSP模拟

    T1 药品实验 内网#4803 由概率定义,有\[a + b + c = 0\] 变形得到\[1 - b = a + c\] 根据题意有\[p_i = a p _{i - 1} + b p_i + c ...

  4. 2019/11/1 CSP模拟

    写在前面的反思 该拿的部分分还是得拿完啊,因为懒+动作慢没有写最后一道题的菊花图和链的情况,其实这两个点并不难.. 虽然只有\(10pts\),但是已经足够往上爬一截了啊,额外的\(10pts\)在今 ...

  5. 『2019/4/8 TGDay1模拟赛 反思与总结』

    2019/4/8 TGDay1模拟赛 这次是和高一的学长学姐们一起参加的\(TG\)模拟考,虽然说是\(Day1\),但是难度还是很大的,感觉比\(18\)年的\(Day1\)难多了. 还是看一下试题 ...

  6. 『2019/4/9 TGDay2模拟赛 反思与总结』

    2019/4/9 TGDay2模拟赛 今天是\(TG\)模拟赛的第二天了,试题难度也是相应地增加了一些,老师也说过,这就是提高组的难度了.刚开始学难的内容,一道正解也没想出来,不过基本的思路也都是对了 ...

  7. 11/1 NOIP 模拟赛

    11.1 NOIP 模拟赛 期望得分:50:实际得分:50: 思路:暴力枚举 + 快速幂 #include <algorithm> #include <cstring> #in ...

  8. CSP模拟赛游记

    时间:2019.10.5 考试时间:100分钟(连正式考试时间的一半还没有到)题目:由于某些原因不能公开. 由于第一次接触NOIinux系统所以连怎么建文件夹,调字体,如何编译都不知道,考试的前半小时 ...

  9. 11.7 NOIP模拟赛

    目录 2018.11.7 NOIP模拟 A 序列sequence(two pointers) B 锁lock(思路) C 正方形square(埃氏筛) 考试代码 B C 2018.11.7 NOIP模 ...

随机推荐

  1. yarn安装node-sass报错问题

    react前端项目在用yarn install命令安装依赖时报错了,看了下报错信息是node-sass安装的时候编译报错. 解决方法: 第一步:配置淘宝镜像 yarn config set regis ...

  2. React 组件间传值

    壹  .了解React传值的数据 一. 创建组件的方法 一 . 1  通过function声明的组件特点是: 1)function创建的组件是没有state属性,而state属性决定它是不是有生命周期 ...

  3. 【命令】GETBIT/SETBIT

    我们在登陆某些博客网站或者视频网站的时候,网站往往会记录我们是否阅读了某篇文章,或者是观看了某个视频. 如果用传统的mysql数据库实现,如果用户数量多,文章和视频也多的情况下,那么则会给数据库带来很 ...

  4. Ubuntu 18.04 安装 python3.7

    Ubuntu 18.04系统内置了Python 3.6和Python 2.7版本,以下是在Ubuntu 18.04系统中安装Python 3.7版本的方法. 1. 执行所有升级# sudo apt u ...

  5. PHP ftp_pasv() 函数

    定义和用法 ftp_pasv() 函数把被动模式设置为打开或关闭. 在被动模式中,数据连接是由客户机来初始化的,而不是服务器.这在客户机位于防火墙之后时比较有用. 语法 ftp_pasv(ftp_co ...

  6. 【LeetCode 20】有效的括号

    题目链接 [题解] 一道傻逼括号匹配题 [代码] class Solution { public: bool isValid(string s) { vector<char> v; int ...

  7. 质数密度+思维——cf1174D

    /* 构造 n个点的无向图,无重边自环 边数e也是质数 点的度数也是质数 */ #include<bits/stdc++.h> #include<vector> using n ...

  8. delphi 下载

    获取网络文件大小 //delphi 获取网络文件大小 function GetUrlFileSize(aURL: string): integer; var FileSize: integer; va ...

  9. [NOIP模拟16]题解

    A.Blue 出题人大概已经去为国家处理积压子弹了? 贪心,让每一只青蛙(我怂行吧)都尽量往远跳,能到达的最远的被踩了就跳次远的,以此类推.可以维护一个单调队列,表示每只青蛙的位置(开始都是0).然后 ...

  10. 2019牛客多校第四场C-sequence(单调栈+线段树)

    sequence 题目传送门 解题思路 用单调栈求出每个a[i]作为最小值的最大范围.对于每个a[i],我们都要乘以一个以a[i]为区间内最小值的对应的b的区间和s,如果a[i] > 0,则s要 ...