• 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 << '
    ';
    	for (register int i = 1; i <= cnt; ++i) cout << ope[i].x << " " << ope[i].y << '
    ';
    	return 0;
    }
    

    T2

    二分+主席树验证
    此题的(trick)有学习意义,(kma)太菜了并没有见过
    暴力分可以贪心,发现显然只有最大的一个对答案有影响,所以每次抓最大的来减即可
    正解
    二分一下最终答案(len),每秒减少的值并不是问题,在(check)的时候把这个量加上去就行
    考虑(check)(s)次以内能不能把所有小麦值操作到(len)以下
    首先我们需要(check)的只有(a_i > len)的,因为剩下的对答案没影响
    那么我们最终的操作次数(times = sumlimits_{a_i > len}lceilfrac{a_i - len}{x} ceil)
    发现这个式子并不好维护到单次查询(O(logn))的复杂度,也不能直接强拆,考虑巧妙地拆开分别维护
    定义({a})表示实数(a)的小数部分
    将上式变形得到

    [egin{aligned}times &= sumlimits_{a_i > len}lceillfloor{frac{a_i}{x} floor + {frac{a_i}{x}} - lfloorfrac{len}{x} floor-{frac{len}{x}}} ceil \ &= sumlimits_{a_i > len}lceil(lfloor{frac{a_i}{x} floor - lfloorfrac{len}{x} floor ) + ({frac{a_i}{x}} - {frac{len}{x}})} ceil \ &= sumlimits_{a_i > len}{(lfloor{frac{a_i}{x} floor - lfloorfrac{len}{x} floor ) + lceil ({frac{a_i}{x}} - {frac{len}{x}})} ceil} \ &= sumlimits_{a_i > len}{(lfloor{frac{a_i}{x} floor - lfloorfrac{len}{x} floor ) + [(a_i mod x) > (len mod x)}]} end{aligned} ]

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

    (sumlimits_{a_i < len} lfloorfrac{a_i}{x} floor)(a_i)排序之后是一个后缀和的形式,预处理之后每次询问二分找一下这个点,(O(logn))

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

    (sumlimits_{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
    ", 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))

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

    其中(sumlimits_{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;
    }
    
  • 相关阅读:
    o1-check-power-of-2 && search-a-2d-matrix
    Remove Linked List Elements &&remove-duplicates-from-sorted-list
    fibonacci && climbing-stairs
    2016 Multi-University Training Contest 1 Abandoned country
    中断调用与子程序设计实验
    POJ 3764
    CSU 1566 The Maze Makers
    poj2524 Ubiquitous Religions
    poj2377
    汇编语言程序设计的上机基础知识预备
  • 原文地址:https://www.cnblogs.com/kma093/p/11852021.html
Copyright © 2020-2023  润新知