2019/11/12 CSP模拟赛&&考前小总结

写在前面的总结

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

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

像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;
}
posted @ 2019-11-13 19:25  kma_093  阅读(184)  评论(0编辑  收藏  举报