• skicean's practice contest (1)


    这场比赛是我自己找的我没做过的题,我给我自己出的比赛(我可以随便更改总时间。。)。
    这可能是最后一场skicean的practice contest了,因为skicean觉得把一些题合起来出成一场不限时的contest没有意义。

    比赛链接

    A - Painting Machines

    这是一道有思维含量的计数题

    题目叙述

    (n) 个排成一行的格子。前 (n-1) 个位置有机器人。每个机器人会涂向后两个格子。每个机器人都运作一遍算是一种涂法,它的贡献为最后一个涂上的格子是第几次涂的。

    题解

    很容易想到,如果得到贡献为 (i) 的排列方法数量,就解决了。但是这并不好得到。所以考虑求 (f_i) 表示 (i) 步已经涂完的方案数量。这样差分一下就可以得到贡献为 (i) 的排列方法数量了。

    首先,必然第一个机器人。如果得到一共有多少个选择机器人的方案,那么乘上排列机器人与剩下机器人的排列方案数就是答案了。所以问题转化为如何得到一共有多少个选择机器人的方案,使得它可以覆盖所有的格子。考虑将所有机器人按照起始位置排序,那么考虑从小到大将每个机器人逐个添加。添加过程中,每个机器人至少能多涂一个格子,至多涂两个格子。如果确定每一次添加机器人能多多少个格子,那么可以唯一对应一种每个位置的机器人选不选的方案。如果我们知道一共涂了多少次就涂完了,那么就可以求出一共有多少次添加机器人能够多1个格子,有多少次能够多2个格子。所以设有 (x) 个多一个格子的,(y) 个多两个格子的。那么方案数为 (inom{x+y}{x})。然后乘上系数就完了。

    总结

    关键在于找到一个合适的对应关系。一个+1,+2组成的数列可以对应一个机器人选不选的方案。

    代码

    #include <cstdio>
    using namespace std;
    const int N = 1e6 + 5, Mod = 1e9 + 7;
    int ml(int x, int y) { return 1ll * x * y % Mod; }
    int dc(int x, int y) { return (x - y < 0) ? (x - y + Mod) : (x - y); }
    int ad(int x, int y) { return (x + y > Mod) ? (x + y - Mod) : (x + y); }
    int n, f[N], fac[N], inv[N];
    int ksm(int x, int y) {
    	int ret = 1;
    	for (; y; y >>= 1, x = ml(x, x))
    		if (y & 1) ret = ml(ret, x);
    	return ret;
    }
    void Prepare() {
    	fac[0] = 1;
    	for (int i = 1; i <= n; ++i) fac[i] = ml(fac[i - 1], i);
    	inv[n] = ksm(fac[n], Mod - 2);
    	for (int i = n - 1; i >= 0; --i) inv[i] = ml(inv[i + 1], i + 1);
    }
    int binom(int u, int d) {
    	if (u < 0 || d < 0) return 0;
    	if (u < d) return 0;
    	return ml(fac[u], ml(inv[d], inv[u - d]));
    }
    int main() {
    	scanf("%d", &n);
    	Prepare();
    	for (int i = 1; i <= n - 1; ++i)
    		f[i] = ml(binom(i - 1, n - i - 1), ml(fac[i], fac[n - i - 1]));
    	for (int i = n - 1; i >= 1; --i) f[i] = dc(f[i], f[i - 1]);
    	int ans = 0;
    	for (int i = 1; i <= n - 1; ++i) ans = ad(ans, ml(f[i], i));
    	printf("%d
    ", ans);
    	return 0;
    }
    

    B - Odd-Even Subsequence

    题目叙述

    给你一个数列,求所有长度为 (k) 的子数列(以 (a) 为例)的 (min{max{a_1,a_3,cdots},max{ a_2,_3,cdots}}) 的最小值。

    题解

    考虑二分 (min{max{a_1,a_3,cdots},max{ a_2,a_3,cdots}}le x) 是否可行。

    那么只要在所有奇数项和偶数项中有一个的最大值(le x)就可以了。把序列转化为 (0/1) 组成的序列,如果 (a_ile x) 那么对应 (1) ,否则是 (0) 。那就看是否可以只选择 (1) 的,并且能构成奇数项/偶数项。那么维护每一个位置向后的第一个0/1所在的位置,贪心选就行了。

    总结

    没啥。写代码的时候注意写对,不要为了简洁而使用玄妙写法。

    代码

    #include <cstdio>
    using namespace std;
    const int N = 2e5 + 5;
    int n, k, seq[N], odd, even, nxt[N];
    bool ho[N];
    bool source(int lp, int rp, int need) {
    	if (rp < lp) return 0;
    	int now = rp + 1;
    	for (int i = rp; i >= lp; --i) {
    		if (ho[i]) now = i;
    		nxt[i] = now;
    	}
    	now = nxt[lp];
    	if (now == rp + 1) return 0;
    	int cnt = 0;
    	while (now <= rp) {
    		++cnt;
    		if (now <= rp - 2)
    			now = nxt[now + 2];
    		else
    			break ;
    	}
    	return cnt >= need;
    }
    bool checkOdd() {
    	int bg = 1, ed = (k & 1) ? n : (n - 1);
    	return source(bg, ed, odd);
    }
    bool checkEven() {
    	int bg = 2, ed = (k & 1) ? (n - 1) : n;
    	return source(bg, ed, even);
    }
    bool check(int ans) {
    	for (int i = 1; i <= n; ++i) ho[i] = (seq[i] <= ans);
    	return checkOdd() | checkEven();
    }
    int main() {
    	scanf("%d%d", &n, &k);
    	if (k & 1) {
    		odd = (k + 1) >> 1;
    		even = k >> 1;
    	} else {
    		odd = k >> 1;
    		even = k >> 1;
    	}
    	for (int i = 1; i <= n; ++i) scanf("%d", &seq[i]);
    	int L = 1, R = 1e9, ans = 1e9;
    	while (L <= R) {
    		int mid = (L + R) >> 1;
    		if (check(mid)) {
    			R = mid - 1;
    			ans = mid;
    		} else
    			L = mid + 1;
    	}
    //	printf("check(1) : %d
    ", check(1));
    	printf("%d
    ", ans);
    	return 0;
    }
    /*
    9 6
    61893 41300 6953 17157 3356 96839 77399 31252 37704
    */
    

    C - Binary Subsequence Rotation

    题目叙述

    现在有两个长度相同01组成的序列,每次可以选择第一个序列的一个子序列让他们变成下一个轮换。比如11001轮换就是10011(a_i ightarrow a_{i+1})(a_n ightarrow a_1))。问最少多少次可以将第一个序列变成第二个。

    题解

    可以发现如果选择的子序列存在相邻两个位置上的数相同,那么这次轮换会有一些位置并没有改变。所以可以考虑将靠后的那个位置去掉(不参与这次变换),这样选择的子序列必然是一个相邻不同的子序列。那么一次变换就是一次等价于选择一些不同相邻不同的(并且0和1的总数量一样)的子序列每个位置取反。需要取反的只有这两个序列的不同值得位置,所以把相同的去掉即可。

    所以有一个想法是答案$ge $最长连续相同的长度(其实这个想法和 这题 比较像)。但是这样你会发现这样会wa on test 10。仔细一想,这实际上是一个错误算法,反例...挺好造的。那么怎么办呢?考虑贪心。每次选择一个位置,每回选择下一个还没有变成第二个序列、与当前位置值不相同的位置,然后将这些选上的位置操作一下(这些位置的值就变正确了),++ans

    那么问题就是如何进行这些操作。考虑维护两个set,每个维护值为0的位置集合与值为1的位置集合(并且这些位置的数目前都不正确)。每次二分到下一个位置,然后将这个位置从set中去掉。

    大概就是这样,还有一些细节。复杂度(mathcal O(nlog _2n))

    总结

    猜玄学结论之前要验证一下。。

    代码

    #include <cstdio>
    #include <iostream>
    #include <set>
    #include <vector>
    using namespace std;
    typedef set<int>::iterator sit;
    const int N = 1e6 + 5;
    int n, m, ans;
    char A[N], B[N];
    int s[N];
    set<int> ps[2];
    bool vis[N];
    int main() {
    	scanf("%d%s%s", &n, A + 1, B + 1);
    	for (int i = 1; i <= n; ++i)
    		if (A[i] != B[i])
    			s[++m] = A[i] - '0';
    	for (int i = 1; i <= m; ++i)
    		ps[s[i]].insert(i);
    	for (int i = 1; i <= m; ++i) {
    		if (vis[i]) continue ;
    		++ans;
    		int now = i, lst = 0, cnt = 0;
    		sit tmp;
    		while(1) {
    			++cnt;
    			if ((tmp = ps[s[now] ^ 1].upper_bound(now)) == ps[s[now] ^ 1].end() && (cnt & 1)) {
    				--cnt;
    				break ;
    			}
    			ps[s[now]].erase(now);
    			lst = now;
    			vis[now] = 1;
    			if (tmp == ps[s[now] ^ 1].end())
    				break ;
    			now = *tmp;
    		}
    	}
    	for (int i = 1; i <= m; ++i)
    		if (!vis[i]) {
    			printf("-1
    ");
    			return 0;
    		}
    	printf("%d
    ", ans);
    	return 0;
    }
    

    D - TediousLee

    题目叙述

    详见题面。懒于描述。。。

    题解

    (f_i=2f_{i-2}+f_{i-1}+[imod 3=0]cdot 4)

    肯定是从下向上选claw最优。所以就是看 (i) 阶的最考上的claw是否能选,容易发现 (imod 3=0) 时可选。

    总结

    多组询问可以直接把答案统一处理出来。。。

    代码

    #include <cstdio>
    using namespace std;
    const int N = 2e6 + 5, Mod = 1e9 + 7;
    int ml(int x, int y) { return 1ll * x * y % Mod; }
    int ad(int x, int y) { return (x + y > Mod) ? (x + y - Mod) : (x + y); }
    int t, f[N];
    int main() {
    	for (int i = 1; i <= N - 5; ++i)
    		f[i] = ad(ml(2, f[i - 2]), ad(f[i - 1], (i % 3 == 0) ? 1 : 0));
    	scanf("%d", &t);
    	while (t--) {
    		int l;
    		scanf("%d", &l);
    		printf("%d
    ", ml(f[l], 4));
    	}
    	return 0;
    }
    
  • 相关阅读:
    cisco/CCNA思科静态路由配置(附PKA文件)
    Web前端常用词汇大全
    解决Linux无法安装pygame问题
    CC2530常用的控制寄存器
    解决MySQL外键约束中的引用列和引用列不兼容问题
    详解使用Hyper-V安装Ubuntu Server 16.10
    虚拟机VMware下CentOS6.6安装教程图文详解
    word论文排版技法之五——标题样式关联多级列表
    如何写《软件需求规格说明书》
    VisualStudio官网使用教程
  • 原文地址:https://www.cnblogs.com/skiceanAKacniu/p/13233092.html
Copyright © 2020-2023  润新知