• AtCoder Grand Contest 002


    题目传送门:AtCoder Grand Contest 002

    A - Range Product

    略。

    #include <cstdio>
    
    int A, B;
    
    int main() {
    	scanf("%d%d", &A, &B);
    	if (A <= 0 && B >= 0) puts("Zero");
    	else if (A > 0 || (B - A) & 1) puts("Positive");
    	else puts("Negative");
    	return 0;
    }
    

    B - Box and Ball

    在任意时刻,维护 (mathrm{num}[i]) 表示第 (i) 个盒子内的球数,(mathrm{typ}[i]) 表示第 (i) 个盒子内是否有红球:(0) 一定没有,(1) 一定有,(2) 两种都有可能。

    转移显然,最终 (mathrm{typ}) 非零的位置的个数就是答案。

    #include <cstdio>
    
    const int MN = 100005;
    
    int N, M, Ans;
    int num[MN], typ[MN];
    
    int main() {
    	scanf("%d%d", &N, &M);
    	for (int i = 1; i <= N; ++i)
    		num[i] = 1, typ[i] = i == 1;
    	for (int i = 1; i <= M; ++i) {
    		int x, y;
    		scanf("%d%d", &x, &y);
    		if (num[x] == 1) {
    			if (typ[x] == 1) typ[y] = 1;
    			else if (typ[x] == 2) typ[y] = 2;
    			typ[x] = 0;
    		} else if (typ[x] != 0) typ[x] = typ[y] = 2;
    		--num[x], ++num[y];
    	}
    	for (int i = 1; i <= N; ++i) if (typ[i]) ++Ans;
    	printf("%d
    ", Ans);
    	return 0;
    }
    

    C - Knot Puzzle

    把操作过程倒过来,相当于每次合并两个相邻的,需要保证合并后的长度至少为 (L)

    如果任意两个相邻的长度之和都比 (L) 小的话那一定无解,否则第一次合并那两个然后一直向左向右合并完即可。注意倒序输出。

    #include <cstdio>
    
    const int MN = 100005;
    
    int N, L, A[MN];
    
    int main() {
    	scanf("%d%d", &N, &L);
    	for (int i = 1; i <= N; ++i) scanf("%d", &A[i]);
    	int ok = 0;
    	for (int i = 1; i < N; ++i) if (A[i] + A[i + 1] >= L) ok = i;
    	if (!ok) return puts("Impossible"), 0;
    	puts("Possible");
    	for (int i = 1; i < ok; ++i) printf("%d
    ", i);
    	for (int i = N - 1; i >= ok; --i) printf("%d
    ", i);
    	return 0;
    }
    

    D - Stamp Rally

    对询问做整体二分,用可撤销并查集维护连通性以及连通块大小,时间复杂度为 (mathcal O ((M + Q) log M log N))

    #include <cstdio>
    #include <algorithm>
    
    const int MN = 100005, MM = 100005, MQ = 100005;
    
    int N, M, a[MM], b[MM];
    int Q, x[MQ], y[MQ], z[MQ], p[MQ], ans[MQ];
    
    int par[MN], siz[MN], stk[MN], tp;
    inline int fp(int u) {
    	while (par[u]) u = par[u];
    	return u;
    }
    inline void merge(int u, int v) {
    	u = fp(u), v = fp(v);
    	if (u == v) return ;
    	if (siz[u] < siz[v]) std::swap(u, v);
    	par[v] = u, siz[u] += siz[v];
    	stk[++tp] = v;
    }
    inline void undo(int k) {
    	while (tp > k) {
    		int v = stk[tp--];
    		siz[par[v]] -= siz[v];
    		par[v] = 0;
    	}
    }
    
    int tmp[MQ];
    void Solve(int l, int r, int s, int t) {
    	if (s > t) {
    		for (int i = l; i <= r; ++i) merge(a[i], b[i]);
    		return ;
    	}
    	if (l == r) {
    		for (int i = s; i <= t; ++i) ans[p[i]] = l;
    		merge(a[l], b[l]);
    		return ;
    	}
    	int mid = (l + r) >> 1, now = tp;
    	for (int i = l; i <= mid; ++i) merge(a[i], b[i]);
    	int s_ = s, t_ = t;
    	for (int i = s; i <= t; ++i) {
    		int u = fp(x[p[i]]), v = fp(y[p[i]]);
    		int sum = u == v ? siz[u] : siz[u] + siz[v];
    		if (sum >= z[p[i]]) tmp[s_++] = p[i];
    		else tmp[t_--] = p[i];
    	}
    	undo(now);
    	for (int i = s; i <= t; ++i) p[i] = tmp[i];
    	Solve(l, mid, s, t_);
    	Solve(mid + 1, r, s_, t);
    }
    
    int main() {
    	scanf("%d%d", &N, &M);
    	for (int i = 1; i <= M; ++i) scanf("%d%d", &a[i], &b[i]);
    	scanf("%d", &Q);
    	for (int i = 1; i <= Q; ++i) scanf("%d%d%d", &x[i], &y[i], &z[i]), p[i] = i;
    	for (int i = 1; i <= N; ++i) siz[i] = 1;
    	Solve(1, M, 1, Q);
    	for (int i = 1; i <= Q; ++i) printf("%d
    ", ans[i]);
    	return 0;
    }
    

    E - Candy Piles

    每次操作删除最大的一堆,或者把所有堆的石子数量减一。

    如果把 (a) 数组从小到大排序,然后把每一堆的石子摞起来,就像这样:

    那么每次操作就相当于删除最右侧一列或最底部一行,删光了的输掉游戏。

    那么就相当于初始时棋子在右下角,每次能向上或向左一格,谁走出了边界谁就输了。

    我们直接用博弈论中最简单的 PN 态转换来做:P 态(Previous)就是先手必败态,N 态(Next)就是先手必胜态。

    我们规定浅粉色区域(边界外)都是 N 态。所有转移都只能到 N 态的就是 P 态,只要有一种转移能到 P 态的就是 N 态。

    染完色就是这样(金色是 P 态,粉色是 N 态):

    我们无法求出每个格子的状态,但是观察发现:对于每一列,不存在连续两个相邻的 P 态,不存在三个相邻的 N 态。

    而且我们还发现,对于相邻两列,左边一列的颜色向下移动一格,就与右边一列的颜色完全一致。

    这两个结论读者可以自行证明,因为都不难这里不再赘述。

    我们考虑从左向右按列递推,假设当前是第 (i) 列,前一列的高度为 (a_{i - 1}),这一列的高度为 (a_i)

    我们记录每一列的,连续两个 N 态的位置,如上图倒数第二列就是 ({1, 4}) 这两个位置,最后一列就是 ({3, 6}) 这两个位置。

    但是这些位置一直在变化,但是可以发现:基本上都是每次减 (1),然后加入或删除一些位置。

    那么我们可以存储这些位置加上 (i) 的值,比如此时位置为 (p),存储 (p + i),到下一列时位置即为 ((p + i) - (i + 1) = (p - 1))

    接下来处理加入新的一列 (a_i) 时会发生什么:

    如果 (a_{i - 1} = a_i),则仅仅是左边的状态向下移动了一格,然后在最顶上加入一个 P 态而已。否则 (a_{i - 1} < a_i)

    对于 (a_i) 超出 (a_{i - 1}) 的部分,是从第 (a_i) 高度为 P 态开始,往下依次为 PN 态交替,这一部分没有连续两个 N 态。

    那么考虑高度为 (a_{i - 1} + 1) 时的状态:它会和前一列的高度为 (a_{i - 1}) 的状态共同影响该列的高度为 (a_{i - 1}) 的状态。

    这可以直接通过 (a_i - a_{i - 1}) 的奇偶性算出。如果它的状态和前一列的高度为 (a_{i - 1}) 的状态相同,则不会产生任何相邻的 N 态。否则:

    如果它的状态是 P,前一列状态是 N,比如上图中高度为 ([6, 7]) 的相邻两列,就会在 (a_{i - 1} - 1) 高度处产生相邻的 N 态。

    如果它的状态是 N,前一列状态是 P,比如上图中高度为 ([4, 6]) 的相邻两列,就会在 (a_{i - 1}) 高度处产生相邻的 N 态。

    也就是说,每次只会加入最多一个新的相邻的 N 态,而且高度总是比之前的高。

    那么我们可以用一个队列来维护这些连续的 N 态的位置:每次加入新的,如果最低的那个低于高度 (1) 了就把它去掉。

    再来一个变量 (mathrm{lstc}) 表示高度为 (a_i) 时的状态,代码中 (0) 表示 P 态,(1) 表示 N 态。就可以实现转移和求最终答案了。

    时间复杂度为 (mathcal O (N))

    #include <cstdio>
    #include <algorithm>
    
    const int MN = 100005;
    
    int N, A[MN];
    int que[MN], l, r;
    
    int main() {
    	scanf("%d", &N);
    	for (int i = 1; i <= N; ++i) scanf("%d", &A[i]);
    	std::sort(A + 1, A + N + 1);
    	l = 1, r = 0;
    	int lstc = 0;
    	for (int i = 2; i <= N; ++i) {
    		if (A[i] == A[i - 1]) lstc ^= 1;
    		else {
    			int c = ~(A[i] - A[i - 1]) & 1;
    			if (c != lstc) que[++r] = A[i - 1] - lstc + i;
    			lstc = 0;
    		}
    		while (l <= r && que[l] <= i) ++l;
    	}
    	puts(lstc ^ (A[N] & 1) ^ ((r - l) & 1) ? "First" : "Second");
    	return 0;
    }
    

    F - Leftmost Ball

    我们直接对最终的那个产生出的序列进行计数。

    如果 (N = 1)(K = 1),直接输出 (1) 即可。

    那么现在序列中至少有 (1)(2) 两个非零元素了。

    可以发现把序列中非零的数的值做一个置换(也就是 (1)(2)(2)(3)(3)(1) 这种),序列仍然合法。

    那么我们强制计数字典序最小的那种序列的个数,最后把答案乘以 (N!) 输出即可。

    也就是说,必须先出现 (1) 再出现 (2),先出现 (2) 再出现 (3),以此类推。

    再考虑关于 (0) 的合法性:(1) 必须在第一个 (0) 之后出现,(2) 必须在第二个 (0) 之后出现,以此类推。

    也就是:记第 (i)(0) 的位置为 (p_i),以及 (i) 的第一次出现位置为 (q_i)。有:

    1. (p_{i - 1} < p_i)
    2. (q_{i - 1} < q_i)
    3. (p_i < q_i)

    当然还有对于每个颜色 (i in [1, N]),它的后 (K - 2) 次出现,必须按照顺序。

    似乎都是废话,但是其实此时我们已经把问题转化成了,求一张图的拓扑排序的数量的问题:

    上图中,有 (K) 行,和 (N) 列。

    注意到第一个点必须是最左边的 (0),然后接下来可以选第二个 (0) 或者最上面的 (1)

    如果选了最上面的 (1),那下面那条长度为 (K - 2) 的跟着的链可以直接以组合数的形式乘到答案里,然后删掉,变成少第一列的情况。

    如果选了第二个 (0),又变成能选 (1) 或第三个 (0)。如果此时选了 (1),就变成能选 (2) 或第三个 (0) 的情况。以此类推。

    可以发现这个结构是可以 DP 的。

    (mathrm{f}[i][j]) 表示目前 (0) 剩下靠后的 (i) 个,有颜色的点剩下靠后的 (j) 列时的方案数。此时必须有 (j ge i)。转移显然:

    [mathrm{f}[i][j] = mathrm{f}[i - 1][j] + inom{i + j cdot (K - 1) - 1}{K - 2} cdot mathrm{f}[i][j - 1] ]

    答案即为 (mathrm{f}[N][N]),时间复杂度为 (mathcal O (NK + N^2))

    #include <cstdio>
    
    typedef long long LL;
    const int Mod = 1000000007;
    const int MN = 2005, MS = 4000005;
    
    inline int qPow(int b, int e) {
    	int a = 1;
    	for (; e; e >>= 1, b = (LL)b * b % Mod)
    		if (e & 1) a = (LL)a * b % Mod;
    	return a;
    }
    
    int Fac[MS], iFac[MS];
    inline void Init(int N) {
    	Fac[0] = 1;
    	for (int i = 1; i <= N; ++i) Fac[i] = (LL)Fac[i - 1] * i % Mod;
    	iFac[N] = qPow(Fac[N], Mod - 2);
    	for (int i = N; i >= 1; --i) iFac[i - 1] = (LL)iFac[i] * i % Mod;
    }
    inline int Binom(int N, int M) {
    	return (LL)Fac[N] * iFac[M] % Mod * iFac[N - M] % Mod;
    }
    
    int N, K;
    int f[MN][MN];
    
    int main() {
    	scanf("%d%d", &N, &K);
    	if (K == 1 || N == 1) return puts("1"), 0;
    	Init(N * K);
    	f[0][0] = 1;
    	for (int j = 1; j <= N; ++j) f[0][j] = (LL)f[0][j - 1] * Binom(j * (K - 1) - 1, K - 2) % Mod;
    	for (int i = 1; i <= N; ++i) {
    		f[i][i] = f[i - 1][i];
    		for (int j = i + 1; j <= N; ++j)
    			f[i][j] = (f[i - 1][j] + (LL)f[i][j - 1] * Binom(i + j * (K - 1) - 1, K - 2)) % Mod;
    	}
    	printf("%lld
    ", (LL)f[N][N] * Fac[N] % Mod);
    	return 0;
    }
    
  • 相关阅读:
    ckeditor 3.0.1使用
    也谈QQ表情弹出框的制作
    百度的模态弹出窗口
    day03 set集合,文件操作,字符编码以及函数式编程
    写在开始之前
    day07 类的进阶,socket编程初识
    day06 面向对象编程
    day02 Python 的模块,运算,数据类型以及方法
    day04 装饰器 迭代器&生成器 Json & pickle 数据序列化 内置函数
    day08 多线程socket 编程,tcp粘包处理
  • 原文地址:https://www.cnblogs.com/PinkRabbit/p/AGC002.html
Copyright © 2020-2023  润新知