zoukankan      html  css  js  c++  java
  • JXOI 2018 简要题解

    「JXOI2018」游戏

    题意

    可怜公司有 (n) 个办公室,办公室编号是 (lsim 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) 个数全部遍历过就可以结束了,反之不能结束。

    这是很好证明的,因为这些数不遍历的话,那么不存在别的数能把他们消掉。

    反之这些数遍历了,别的数都能通过这些数消掉。

    那么枚举在第几个数遍历完,那么贡献就很好计算了:

    [sum_{i= tot}^{n} A_{i - 1}^{tot - 1} imes (n - tot)! imes i ]

    假设在 (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) 以外的亭子,那么我们认为保镖看不到可怜。

    他想对所有的 (1leq lleq rleq n) 计算:如果事先已知了只有区间 ([l,r]) 的亭子可以用来玩耍(和监视),那么最少需要多少个保镖,才能让 ([l,r]) 中的每一个亭子都被监视到。

    对于 (100\%) 的数据, (nleq 5000, 1leq h_ileq 10^9)

    题解

    参考了 Ebola 大佬的博客

    其实自己想了一下,类似于贪心的思路,看起来很对,但是没写。。参考了一下别人的做法,很优秀嘛。

    由于每个点只能看它左边的节点,我们可以枚举区间 ([l,r]) 的右端点 (r) ,因为 (r) 是必定选择的,然后考虑从右向左枚举 (l) ,记下当前 (r) 能看到的在 (l) 右边的第一个点 (pos)

    我们记 (sum)([pos, r]) 最少需要安放保镖的数量,(dp_{l, r})([l, r]) 这段区间所需要的最少保镖数量。

    所以会需要在 (pos) 或者 (pos - 1) 的位置安放一个保镖才能看到 (l) ,然后转移就是

    [dp_{l, r} = sum + min(dp_{l, pos - 1}, dp_{l, pos}) ]

    每次到一个新的 (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
    ", ans);
    
    	return 0;
    
    }
    

    「JXOI2018」排序问题

    题意

    九条可怜是一个热爱思考的女孩子。

    Gobo sort 是把序列 (A) 不断的执行 std :: random_shuffle 直到序列 (A) 单调不下降的一种奇怪的排序方式。

    于是她就想到了这么一个问题:

    现在有一个长度为 (n) 的序列 (x) ,九条可怜会在这个序列后面加入 (m) 个元素,每个元素是 ([l,r]) 内的正整数。
    她希望新的长度为 (n+m) 的序列执行 Gobo sort 的期望执行轮数尽量的多。

    她希望得到这个最多的期望轮数对 (998244353) 取模的结果。

    对于 (100\%) 的数据, (Tleq 10^5,nleq 2 imes 10^5,mleq 10^7,1leq lleq rleq 10^9, 1leq a_ileq 10^9,sum{n}leq 2 imes 10^6)

    题解

    根据样例可以猜出,期望的次数为单次成功概率 (p) 的倒数,至于证明,可以参考我 这篇博客

    那么意味着 ,(A) 总共能构成的本质不同的序列个数就是期望的次数。

    然后对于一个可重排列的重组个数是很容易计算的。

    假设共有 (n) 种数字,其中第 (i) 种出现的次数为 (p_i) ,那么个数就为

    [displaystyle frac{(sum_{i = 1}^{n}p_i)!}{prod {p_i}!} ]

    就是总排列个数除掉这些数字对应任意排列的排列个数。

    那么不难想到一个贪心的做法,每次从 ([l, r]) 区间内选择一个出现次数 (p_x) 最少的元素 (x) ,当前加入的元素为 (x)

    不难发现这样是正确的,因为我们是需要最大化序列个数,那么意味着每次多除掉的那个的那个元素要尽量小,也就是从 (p_x o p_x + 1) ,多除的那个 (p_x + 1) 要尽量小。

    然后直接这样做是 (displaystyle O(sum_{i = 1}^{T} m_i(r_i - l_i))) 的,显然通过不了。

    由于 (T imes 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
    ", 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
    ", ans);
    	}
    
    #ifdef zjp_shadow
    	cerr << (double) clock() / CLOCKS_PER_SEC << endl;
    #endif
    
    	return 0;
    
    }
    

    总结

    吉老师出的这套题整体难度适中,但不失为具有区分度的一套好题。

    第一题送签到,但也考察了数论的基础、对于性质的观察以及组合数学的基础知识。

    第二题略有难度,但认真观察,还是能推出 (dp)(O(n ^2)) 给了很多不同的做法来选择,贪心或许也是一个可以的思路?

    第三题略微处理细节,整体思路还是较为容易的,出题人没有设较大的难点。

    总体来说,仍然需要提高细节处理能力,以及拥有及时调整开题顺序的想法及手段。

  • 相关阅读:
    innodb buffer pool小解
    information_schema系列十一
    Shader编程教程
    第四章 继承
    第三章 对象和类型
    第二章:核心C#
    前言和第一章.NET的体系结构
    单例模式
    代理模式
    第 1 章 策略模式【Strategy Pattern】
  • 原文地址:https://www.cnblogs.com/zjp-shadow/p/9978542.html
Copyright © 2011-2022 走看看