zoukankan      html  css  js  c++  java
  • AtCoder Grand Contest 014

    题目传送门:AtCoder Grand Contest 014

    显而易见的是,如果可以执行,三个数都会互相接近,必然在 (mathcal O (log)) 步内结束,模拟这个过程。

    #include <cstdio>
    
    int main() {
    	int A, B, C, Ans = 0;
    	scanf("%d%d%d", &A, &B, &C);
    	while (Ans < 99) {
    		if (A & 1 || B & 1 || C & 1) break;
    		int D = B + C, E = A + C, F = A + B;
    		A = D / 2, B = E / 2, C = F / 2;
    		++Ans;
    	}
    	printf("%d
    ", Ans < 99 ? Ans : -1);
    	return 0;
    }
    

    B - Unplanned Queries

    我们将每条边被经过的次数的奇偶性异或到它的两个端点上,每条边被经过偶数次等价于每个端点的权值是 (0)(考虑自下往上)。

    而一条路径仅会让两端点的权值改变,于是这等价于每个点都作为过偶数次的路径端点,可以在 (mathcal O (N)) 的时间内开桶统计。

    #include <cstdio>
    
    const int MN = 100005;
    
    int N, Q, b[MN], x;
    
    int main() {
    	scanf("%d%d", &N, &Q), Q *= 2;
    	while (Q--) scanf("%d", &x), b[x] ^= 1;
    	for (int i = 1; i <= N; ++i) if (b[i]) return puts("NO"), 0;
    	puts("YES");
    	return 0;
    }
    

    C - Closed Rooms

    假设已知最终的行进路径,那么每一次删除 (K) 个障碍物时,都可以删除接下来最近的 (K) 个障碍物,从而保证下一次行走不受阻。

    也就是说除了第一次行走,之后的每一次都可以看做是忽略障碍物的,直接走向最近的出口即可。

    ((x, y)) 出发到达终点需要的步数也就是 (lceil min(x - 1, H - x, y - 1, W - y) / K ceil)

    而判断第一步能否到达 ((x, y)) 只需要做一个 BFS 即可。

    #include <cstdio>
    #include <algorithm>
    
    const int dx[4] = {0, 1, 0, -1};
    const int dy[4] = {1, 0, -1, 0};
    const int MN = 805;
    
    int N, M, K, sx, sy;
    char A[MN][MN];
    int qx[MN * MN], qy[MN * MN], ql, qr;
    int dis[MN][MN];
    int Ans;
    
    int main() {
    	scanf("%d%d%d", &N, &M, &K), Ans = N;
    	for (int i = 1; i <= N; ++i) {
    		scanf("%s", A[i] + 1);
    		for (int j = 1; j <= M; ++j) {
    			dis[i][j] = -1;
    			if (A[i][j] == 'S') sx = i, sy = j;
    		}
    	}
    	dis[sx][sy] = 0, ql = qr = 1, qx[1] = sx, qy[1] = sy;
    	while (ql <= qr) {
    		int x = qx[ql], y = qy[ql]; ++ql;
    		int di = std::min({x - 1, N - x, y - 1, M - y});
    		Ans = std::min(Ans, 1 + (di + K - 1) / K);
    		if (dis[x][y] == K) continue;
    		for (int d = 0; d < 4; ++d) {
    			int nx = x + dx[d], ny = y + dy[d];
    			if (nx < 1 || nx > N || ny < 1 || ny > M || A[nx][ny] == '#' || ~dis[nx][ny]) continue;
    			dis[nx][ny] = dis[x][y] + 1;
    			++qr, qx[qr] = nx, qy[qr] = ny;
    		}
    	}
    	printf("%d
    ", Ans);
    	return 0;
    }
    

    D - Black and White Tree

    考虑一个叶子,先手把这个叶子连接的那个点染白,则后手必须染黑那个叶子,否则下一步先手即可把叶子染白赢得游戏。

    考虑如果有一个点连接了两个叶子,那么先手先把它染白,后手就因为无法顾及两个叶子而输掉游戏。

    假设已经不存在这种情况,考虑连续的三个点 (a, b, c),考虑一个时刻 (a, c) 周围的除了 (b) 之外的点全部染白,此时先手将 (b) 染白。

    那么同上,后手将会输掉游戏。我们此时考虑以 (b) 为根时 (a, c) 的子树,如果除了 (a, c) 本身外均有偶数个点,可以证明后手必败。

    这是因为先手可以每次找到 (a)(c) 子树中深度最大的叶子节点,把它的双亲结点染白,逼迫后手染黑那个叶子。

    此时完全等价于把这两个被染色的点从树中删去,用数学归纳法可知最终会删成 (a, c) 无孩子节点的情况,将 (b) 染白赢得游戏。

    也就是说:如果存在一个节点,以它为根时存在至少两个子树方向上的点数为奇数则先手必胜。

    显然如果 (N) 为奇数,任取一个连接叶子节点的点,必存在另一个点数为奇数的子树,这是因为除了它和叶子还有奇数个点。

    所以 (N) 为奇数时先手必胜。如果 (N) 为偶数?我们可以做一次 DFS 来计算是否存在这样的点,如果存在也是先手必胜。

    如果不存在呢?我并不清楚为何不存在时先手必败,此时我直接把代码提交上去就 AC 了(我当时在 virtual participating)。

    赛后查看题解,发现我的 DP 过程(请看代码)竟然等价于求树上是否存在一个完美匹配,即选取一半的边覆盖所有点。

    此时思路就清晰了,如果存在完美匹配,先手每染白一个点,后手就染黑它的匹配点,很显然每个点最终都会被染黑。

    #include <cstdio>
    #include <vector>
    
    const int MN = 100005;
    
    int N;
    std::vector<int> G[MN];
    
    int Ans, siz[MN];
    void DFS(int u, int p) {
    	siz[u] = 1;
    	int s = 0;
    	for (int v : G[u]) if (v != p) {
    		DFS(v, u);
    		s += siz[v];
    		siz[u] ^= siz[v]; 
    	}
    	if (s >= 2) Ans = 1;
    }
    
    int main() {
    	scanf("%d", &N);
    	if (N & 1) return puts("First"), 0;
    	for (int i = 1, x, y; i < N; ++i) {
    		scanf("%d%d", &x, &y);
    		G[x].push_back(y);
    		G[y].push_back(x);
    	}
    	DFS(1, 0);
    	puts(Ans ? "First" : "Second");
    	return 0;
    }
    

    E - Blue and Red Tree

    操作即是删去一条蓝边,然后加上一条红边以连通删去后留下的两个连通块。

    此时加上的红边必须要满足其两端点都处在删蓝边前,那条蓝边所在的通过蓝边能到达的连通子图中。

    考虑删去的第一条蓝边,从蓝树上考虑,显然那条被加入的红边两端点在蓝树上经过的路径覆盖了这条蓝边。

    而且此后就不能再有红边对应的路径覆盖这条蓝边了,否则将违反上述限制。

    那么做法就呼之欲出了:只需找到被覆盖次数恰好为 (1) 的蓝边,以及覆盖它的路径,把路径经过的蓝边的被覆盖次数减去 (1)

    不断重复上述过程直到每条蓝边都被删去即可,如果过程中找不到合法的蓝边则无法成功变换成红树。

    具体实现时使用树链剖分 + 线段树对每条蓝边维护它的被覆盖次数,以及将其覆盖的所有路径的编号的异或和。

    时间复杂度为 (mathcal O (N log^2 N))

    #include <cstdio>
    #include <algorithm>
    #include <vector>
    
    const int Inf = 0x3f3f3f3f;
    const int MN = 100005, MS = 1 << 18 | 7;
    
    int N, ex[MN], ey[MN];
    std::vector<int> G[MN];
    
    int par[MN], dep[MN], siz[MN], pref[MN], top[MN], dfn[MN], dfc;
    void DFS0(int u, int p) {
    	dep[u] = dep[par[u] = p] + 1, siz[u] = 1;
    	for (int v : G[u]) if (v != p) {
    		DFS0(v, u);
    		siz[u] += siz[v];
    		if (siz[pref[u]] < siz[v]) pref[u] = v;
    	}
    }
    void DFS1(int u, int t) {
    	dfn[u] = ++dfc, top[u] = t;
    	if (pref[u]) DFS1(pref[u], t);
    	for (int v : G[u]) if (v != par[u] && v != pref[u]) DFS1(v, v);
    }
    
    #define li (i << 1)
    #define ri (li | 1)
    #define mid ((l + r) >> 1)
    #define ls li, l, mid
    #define rs ri, mid + 1, r
    struct dat {
    	int mn, v, i;
    	dat() { mn = Inf; }
    	dat(int z) { mn = v = 0, i = z; }
    	dat(int x, int y, int z) { mn = x, v = y, i = z; }
    } tr[MS];
    struct tag {
    	int a, w;
    	tag() { a = w = 0; }
    	tag(int x, int y) { a = x, w = y; }
    } tg[MS];
    inline dat operator + (dat a, dat b) { return a.mn < b.mn ? a : b; }
    inline void operator += (tag &a, tag b) { a.a += b.a, a.w ^= b.w; }
    inline void operator += (dat &a, tag b) { a.mn += b.a, a.v ^= b.w; }
    inline void P(int i, tag x) { tr[i] += x, tg[i] += x; }
    void Pushdown(int i) { if (tg[i].a || tg[i].w) P(li, tg[i]), P(ri, tg[i]), tg[i] = tag(); }
    void Build(int i, int l, int r) {
    	if (l == r) return tr[i] = dat(l), void();
    	Build(ls), Build(rs);
    	tr[i] = tr[li] + tr[ri];
    }
    void Mdf(int i, int l, int r, int a, int b, tag x) {
    	if (r < a || b < l) return ;
    	if (a <= l && r <= b) return P(i, x);
    	Pushdown(i);
    	Mdf(ls, a, b, x), Mdf(rs, a, b, x);
    	tr[i] = tr[li] + tr[ri];
    }
    
    inline void Modify(int x, int y, tag z) {
    	while (top[x] != top[y]) {
    		if (dep[top[x]] < dep[top[y]]) std::swap(x, y);
    		Mdf(1, 1, N, dfn[top[x]], dfn[x], z);
    		x = par[top[x]];
    	}
    	if (dep[x] > dep[y]) std::swap(x, y);
    	if (x != y) Mdf(1, 1, N, dfn[x] + 1, dfn[y], z);
    }
    
    int main() {
    	scanf("%d", &N);
    	for (int i = 1, x, y; i < N; ++i) {
    		scanf("%d%d", &x, &y);
    		G[x].push_back(y);
    		G[y].push_back(x);
    	}
    	DFS0(1, 0), DFS1(1, 1);
    	Build(1, 1, N);
    	Mdf(1, 1, N, 1, 1, tag(Inf, 0));
    	for (int i = 1; i < N; ++i) {
    		scanf("%d%d", &ex[i], &ey[i]);
    		Modify(ex[i], ey[i], tag(1, i));
    	}
    	for (int i = 1; i < N; ++i) {
    		if (tr[1].mn != 1) return puts("NO"), 0;
    		int j = tr[1].v;
    		Mdf(1, 1, N, tr[1].i, tr[1].i, tag(Inf, 0));
    		Modify(ex[j], ey[j], tag(-1, j));
    	}
    	puts("YES");
    	return 0;
    }
    

    F - Strange Sorting

    我们注意到一个关键性质:值为 (1) 的元素所在的位置,并不影响其它元素是 high 或者 low,也不影响操作后它们的相对位置。

    这提示我们先删去元素 (1) 然后观察剩下的值在 ([2, N]) 中的元素构成的序列。

    更进一步地我们可以考虑递推:让 (i)(N)(1),每次只考虑值在 ([i, N]) 中的元素构成的序列。

    此时我们以如何从 (i = 2) 转移到 (i = 1) 为例说明算法流程:

    假设 ([2, N]) 内的元素需要恰好 (T) 步排好序。

    如果 (T = 0),也就是说初始时就排好序了,则如果元素 (1) 在开头,答案就为 (0),否则答案为 (1)

    如果 (T ge 1),则考虑包含元素 (1) 的序列在 (T) 步后,元素 (1) 的位置,如果恰好在序列开头答案就为 (T),否则答案为 (T + 1)

    如何判断元素 (1)(T) 步后是否会在序列的开头?

    考察 (T - 1) 步后的情况(注意只有 (T ge 1) 时才有意义,这就是为什么要对 (T) 分类):

    • 引理 (oldsymbol{1})只包含 ([2, N]) 的序列在 (T - 1) 步后的开头的元素一定不是 (2)
    • 证明 (oldsymbol{1})反证法,假设是 (2),且此时序列还未排好序,这等价于存在 low 元素,则再进行一次操作 (2) 必不在开头。

    令开头元素为 (f),上面证明了 (f > 2)

    注意到如果 (T - 1) 步后,元素 (1) 的位置落在 (f)(2) 的位置之间,则再进行一次操作 (1) 就会在开头,否则 (1) 一定不在开头。

    我们需要判断 (T - 1) 步后元素 (1) 的位置是否会在 (f)(2) 之间。

    我们先给出一个结论:如果在初始时元素 (1, 2, f) 的「循环顺序」,恰等于 ((f, 1, 2)),则最终元素 (1) 的位置就会在 (f, 2) 之间。

    此处循环顺序相等,等价于在循环移位下同构。即 ((a, b, c))((b, c, a))((c, a, b)) 相等。

    对这个结论的证明,我们通过证明「在任意时刻下,一次操作均不会改变 (1, 2, f) 的循环顺序」来显示。

    要证明此结论,先证明一个引理(此引理的背景为,忽略元素 (1),即只考虑值在 ([2, N]) 中的元素):

    • 引理 (oldsymbol{2})在前 (T - 1) 步中的任意时刻,除非元素 (f) 作为第一个元素出现,否则 (f) 均不会成为 high 类元素。
    • 也就是说如果 (f) 成为了 high 类元素,当且仅当它处于序列的开头(第一个元素永远是 high 类元素)。
    • 证明 (oldsymbol{2})假设在某一个时刻 (f) 成为了非开头的 high 类元素,操作后它将会处于在它前面的第一个 high 类元素后一位。
    • 此时 (f) 比它前一个元素大,这意味着它们始终会紧挨着,除非它前一个元素为 low 而 (f) 为 high 的情况发生了。
    • 如果它们始终紧挨着,则 (f) 就永远没有机会成为第一个元素,而这正是 (T - 1) 步后所要求的情况。
    • 如果特殊情况发生了,则操作时 (f) 实际上还是一个非开头的 high 类元素,回到初始情况,这种情况不可能无限次发生。

    有了这个结论,我们考虑任意时刻下 (1, 2, f) 的所有情况:

    (注意到如果循环顺序改变,仅有可能是从左到右按照 high, low, high 或者 low, high, low 的顺序排列)

    • 如果 (f) 为开头元素,则 (f) 为 high 类元素,(1, 2) 均为 low 类元素,操作后循环顺序不变。
    • 如果 (2) 为开头元素,则 (2) 为 high 类元素,(1, f) 均为 low 类元素,操作后循环顺序不变。
    • 如果 (1) 为开头元素:
      • 如果 (f) 为第二个元素,则 (1, f) 均为 high 类元素,(2) 为 low 类元素,操作后循环顺序不变。
      • 如果 (2) 为第二个元素,则 (1, 2) 均为 high 类元素,(f) 为 low 类元素,操作后循环顺序不变。
      • 否则 (1) 为 high 类元素,(2, f) 均为 low 类元素,操作后循环顺序不变。
    • 否则 (1, 2, f) 均为 low 类元素,操作后循环顺序不变。

    由此证明了在任意时刻下,一次操作均不会改变 (1, 2, f) 的循环顺序。

    于是证明了初始结论:如果在初始时元素 (1, 2, f) 的循环顺序等于 ((f, 1, 2)),则答案为 (T) 否则为 (T + 1)

    这是从 (i = 2) 推演到 (i = 1) 的情况,对于一般的 (i) 结论是类似的。

    在实现时,对于一个 (i),仅考虑值在 ([i, N]) 中的元素,维护 (T_i) 表示排好序的次数,以及 (f_i) 表示 (T_i - 1) 步后的第一个元素。

    如果 (T_i = 0)(f_i) 视作未定义。

    要计算 (T_i)(f_i) 时:

    • 如果 (T_{i + 1} = 0)
      • 如果元素 (i) 所在的位置在元素 (i + 1) 所在的位置的左边,则 (T_i = 0)(f_i) 未定义。
      • 否则 (T_i = 1)(f_i = i + 1)
    • 否则 (T_{i + 1} ge 1)
      • 如果元素 (i)、元素 (i + 1) 与元素 (f_{i + 1}) 所在的位置的循环顺序是 ((f_{i + 1}, i, i + 1)),则 (T_i = T_{i + 1})(f_i = f_{i + 1})
      • 否则 (T_i = T_{i + 1} + 1)(f_i = i + 1)

    最后输出 (T_1) 作为答案即可。

    #include <cstdio>
    
    const int MN = 200005;
    
    int N, p[MN], q[MN];
    int t[MN], f[MN];
    
    int main() {
    	scanf("%d", &N);
    	for (int i = 1; i <= N; ++i) scanf("%d", &p[i]), q[p[i]] = i;
    	t[N] = 0;
    	for (int i = N - 1; i >= 1; --i) {
    		if (t[i + 1] == 0) {
    			if (q[i] > q[i + 1]) t[i] = 1, f[i] = i + 1;
    			else t[i] = 0;
    		} else {
    			if ( /* 1. */ (q[i] < q[i + 1] && q[i + 1] < q[f[i + 1]]) ||
    			/*      2. */ (q[i + 1] < q[f[i + 1]] && q[f[i + 1]] < q[i]) ||
    			/*      3. */ (q[f[i + 1]] < q[i] && q[i] < q[i + 1]))
    				t[i] = t[i + 1], f[i] = f[i + 1];
    			else t[i] = t[i + 1] + 1, f[i] = i + 1;
    		}
    	}
    	printf("%d
    ", t[1]);
    	return 0;
    }
    
  • 相关阅读:
    2020-2021-1 20201221 《信息安全专业导论》第五周学习总结
    XOR加密
    2020-2021-1 20201221 《信息安全专业导论》第四周学习总结
    [SQL]创建数据库
    [SQL]基本表的定义及其完整性约束
    [SQL]修改和删除基本表
    [SQL]连接查询
    [SQL]嵌套查询
    机器学习中常用的求导公式
    [C++]-unordered_map 映射
  • 原文地址:https://www.cnblogs.com/PinkRabbit/p/AGC014.html
Copyright © 2011-2022 走看看