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

    题目传送门:AtCoder Grand Contest 018

    A - Getting Difference

    (gcd(A_1, A_2, ldots , A_N) mid K)(K le max(A_1, A_2, ldots , A_N)) 时输出 POSSIBLE

    #include <cstdio>
    #include <algorithm>
    
    int N, K, g, mx;
    
    int main() {
    	scanf("%d%d", &N, &K);
    	for (int i = 1, x; i <= N; ++i)
    		scanf("%d", &x),
    		g = std::__gcd(g, x),
    		mx = std::max(mx, x);
    	puts(K <= mx && K % g == 0 ? "POSSIBLE" : "IMPOSSIBLE");
    	return 0;
    }
    

    B - Sports Festival

    我们假设一开始所有项目都举办了,此时考虑一个当前时刻参与人数最多的项目。

    如果这个项目不去掉,则答案永远不会比这个项目此时的参与人数少。那么必须去掉它。

    一直这样去掉,直到只剩一个了,在这之间求最小值即可。

    #include <cstdio>
    
    const int MN = 305, MM = 305;
    
    int N, M, A[MN][MM], cho[MM], p[MN], buk[MM], Ans;
    
    int main() {
    	scanf("%d%d", &N, &M), Ans = N;
    	for (int i = 1; i <= N; ++i)
    		for (int j = 1; j <= M; ++j)
    			scanf("%d", &A[i][j]);
    	for (int j = 1; j <= M; ++j) cho[j] = 1;
    	for (int i = 1; i <= N; ++i) p[i] = 1;
    	for (int k = 1; k <= M; ++k) {
    		for (int j = 1; j <= M; ++j) buk[j] = 0;
    		for (int i = 1; i <= N; ++i) {
    			while (!cho[A[i][p[i]]]) ++p[i];
    			++buk[A[i][p[i]]];
    		}
    		int mxi = 0;
    		for (int j = 1; j <= M; ++j)
    			if (buk[mxi] < buk[j]) mxi = j;
    		if (Ans > buk[mxi]) Ans = buk[mxi];
    		cho[mxi] = 0;
    	}
    	printf("%d
    ", Ans);
    	return 0;
    }
    

    C - Coins

    我们假装全部选 (A),然后把 (B, C) 都减去 (A)

    那就是要选 (Y)(B)(Z)(C),最大化收益。

    那再对所有数对 ((B, C)) 按照 (B) 的值从大到小排序。

    那么此时我们钦点了一些位置选了 (C),则就是尽量前面的没有选 (C) 的位置选了 (B)

    那么我们在 (Y sim Y + Z) 之间钦点一个位置,它是最后一个 (B) 的选取位置。

    那么现在又变成了:
    先钦点这个位置之前的全选 (B),要在左边换一些变成 (C),剩下恰好 (Y)(B)
    先钦点右边全没选,也要在右边换一些变成 (C),剩下恰好 (X) 个位置没选。

    这个其实是经典题了,左侧的问题,就用大小 (= Y) 的一个优先队列维护。
    右侧就是用 (= X) 的优先队列维护。

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

    #include <cstdio>
    #include <algorithm>
    #include <queue>
    
    typedef long long LL;
    const LL Inf = 0x3f3f3f3f3f3f3f3f;
    const int MN = 100005;
    
    int N, X, Y, Z, B[MN], C[MN], P[MN];
    LL Pre[MN], Suf[MN];
    std::priority_queue<LL> pq;
    LL Sum, Ans;
    
    int main() {
    	scanf("%d%d%d", &X, &Y, &Z), N = X + Y + Z, Ans = -Inf;
    	for (int i = 1; i <= N; ++i) {
    		int a;
    		scanf("%d%d%d", &a, &B[i], &C[i]);
    		B[i] -= a, C[i] -= a, P[i] = i;
    		Sum += a;
    	}
    	std::sort(P + 1, P + N + 1, [](int i, int j) { return B[i] > B[j]; });
    	for (int i = 1; i <= Y + Z; ++i) {
    		Pre[i] = Pre[i - 1] + B[P[i]];
    		pq.push(C[P[i]] - B[P[i]]);
    		if ((int)pq.size() > Y) Pre[i] += pq.top(), pq.pop();
    	}
    	while (!pq.empty()) pq.pop();
    	for (int i = N; i > Y; --i) {
    		Suf[i] = Suf[i + 1];
    		pq.push(C[P[i]]);
    		if ((int)pq.size() > X) Suf[i] += pq.top(), pq.pop();
    	}
    	for (int i = Y; i <= Y + Z; ++i)
    		Ans = std::max(Ans, Pre[i] + Suf[i + 1]);
    	printf("%lld
    ", Sum + Ans);
    	return 0;
    }
    

    D - Tree and Hamilton Path

    这是哈密顿路径,如果是哈密顿回路呢?

    一条边能在哈密顿回路中被经过的次数,显然有一个上界是 (2 S_i C_i),其中 (S_i) 是第 (i) 条边两端的子树的较小大小,(C_i) 是权值。

    关于构造,考虑在重心处构造即可,让每棵子树都不并列。(好像是经典题了)

    那哈密顿路径就是哈密顿回路去掉一条边。显然我们去掉所有可能性中能让答案最小的边。

    可以看出本题中重心是很重要的,众所周知,有两类重心:

    1. 重心在边上,也就是一条边去掉后可以把两侧分成大小恰好为 (N / 2) 的两棵子树:
      这条边此时显然最多只能经过 (N - 1) 次了,那么答案肯定不超过 (sum 2 S_i C_i - C_v)(其中 (v) 就是这条边)。
      而这个又是能够构造的,本文略。
    2. 重心在点上:
      此时考虑与该点相邻的所有边 (e_1, e_2, ldots , e_k)
      我们可以证明这些边之中必须有一条边相比哈密顿回路时的情况,少被经过一次。
      同理我们选择一个 (C) 值最小的那条边删掉即可。
    #include <cstdio>
    #include <algorithm>
    #include <vector>
    
    typedef long long LL;
    const int MN = 100005;
    
    int N;
    std::vector<std::pair<int, int>> G[MN];
    
    LL Ans; int WTF;
    int siz[MN], rt, rts;
    void DFS(int u, int p, int x) {
    	siz[u] = 1;
    	int mxs = 0;
    	for (auto e : G[u]) if (e.first != p) {
    		int v = e.first;
    		DFS(v, u, e.second), siz[u] += siz[v];
    		mxs = std::max(mxs, siz[v]);
    	}
    	if (2 * siz[u] == N) WTF = x;
    	Ans += std::min(siz[u], N - siz[u]) * (LL)x;
    	mxs = std::max(mxs, N - siz[u]);
    	if (rts > mxs) rt = u, rts = mxs;
    }
    
    int main() {
    	scanf("%d", &N);
    	for (int i = 1, x, y, z; i < N; ++i) {
    		scanf("%d%d%d", &x, &y, &z);
    		G[x].push_back({y, z});
    		G[y].push_back({x, z});
    	}
    	rts = N, DFS(1, 0, 0);
    	if (!WTF) {
    		WTF = 0x3f3f3f3f;
    		for (auto e : G[rt]) WTF = std::min(WTF, e.second);
    	}
    	printf("%lld
    ", Ans * 2 - WTF);
    	return 0;
    }
    

    E - Sightseeing Plan

    如果固定中转点。那么方案数就是每个起点到中转点,和每个终点到中转点,的方案数的乘积。

    那么一个矩形的起点,到一个点,的方案数。其实就是杨辉三角上一个菱形的和。

    其实就是四个点到一个点的方案数(但是系数是两个 (+1) 两个 (-1))。

    那么现在就转化成 (4 imes 4 = 16) 种情况的,起点和终点都是唯一的,但是中转点是在一个区域内的情况。

    此时就相当于:对每个起点到终点的每条路径,把经过那个区域的长度求和。

    此时又是一个很重要的转换:经过那个区域的长度转化成,离开那个区域时的坐标,减去进入那个区域时的坐标(曼哈顿距离)。

    那么我们就可以枚举离开那个区域时的位置,求出满足的路径数,乘以曼哈顿坐标贡献给答案。

    进入那个区域的情况同理。

    预处理组合数,时间复杂度就是坐标范围。

    #include <cstdio>
    #include <algorithm>
    
    typedef long long LL;
    const int Mod = 1000000007;
    const int MV = 2000005;
    
    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;
    }
    inline int gInv(int b) { return qPow(b, Mod - 2); }
    
    int Fac[MV], iFac[MV];
    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] = gInv(Fac[N]);
    	for (int i = N; i >= 1; --i) iFac[i - 1] = (LL)iFac[i] * i % Mod;
    }
    inline int Binom(int N, int M) {
    	if (M < 0 || M > N) return 0;
    	return (LL)Fac[N] * iFac[M] % Mod * iFac[N - M] % Mod;
    }
    inline int Calc(int N, int M) {
    	return Binom(N + M, N);
    }
    
    int X1, X2, X3, X4, X5, X6;
    int Y1, Y2, Y3, Y4, Y5, Y6;
    
    int sx[4], sy[4], tx[4], ty[4];
    int Ans;
    
    int main() {
    	Init(2000000);
    	scanf("%d%d%d%d%d%d", &X1, &X2, &X3, &X4, &X5, &X6);
    	scanf("%d%d%d%d%d%d", &Y1, &Y2, &Y3, &Y4, &Y5, &Y6);
    	sx[0] = X1 - 1, sy[0] = Y1 - 1;
    	sx[1] = X1 - 1, sy[1] = Y2;
    	sx[2] = X2, sy[2] = Y2;
    	sx[3] = X2, sy[3] = Y1 - 1;
    	tx[0] = X6 + 1, ty[0] = Y6 + 1;
    	tx[1] = X6 + 1, ty[1] = Y5;
    	tx[2] = X5, ty[2] = Y5;
    	tx[3] = X5, ty[3] = Y6 + 1;
    	for (int sk = 0; sk < 4; ++sk) {
    		for (int tk = 0; tk < 4; ++tk) {
    			int spx = sx[sk], spy = sy[sk];
    			int tpx = tx[tk], tpy = ty[tk];
    			int coef = (sk ^ tk) & 1 ? -1 : 1;
    			int Sum = 0;
    			for (int i = X3; i <= X4; ++i) {
    				Sum = (Sum + (LL)Calc(i - spx, Y3 - 1 - spy) * Calc(tpx - i, tpy - Y3) % Mod * -(i + Y3 - 1)) % Mod;
    				Sum = (Sum + (LL)Calc(i - spx, Y4 - spy) * Calc(tpx - i, tpy - Y4 - 1) % Mod * (i + Y4)) % Mod;
    			}
    			for (int j = Y3; j <= Y4; ++j) {
    				Sum = (Sum + (LL)Calc(X3 - 1 - spx, j - spy) * Calc(tpx - X3, tpy - j) % Mod * -(X3 - 1 + j)) % Mod;
    				Sum = (Sum + (LL)Calc(X4 - spx, j - spy) * Calc(tpx - X4 - 1, tpy - j) % Mod * (X4 + j)) % Mod;
    			}
    			Ans = (Ans + coef * Sum) % Mod;
    		}
    	}
    	printf("%d
    ", (Ans + Mod) % Mod);
    	return 0;
    }
    

    F - Two Trees

    首先声明 (X) 值只有可能取到 ({-1, 0, 1})

    我们可以根据孩子个数确定 (X_i) 的奇偶性。如果两棵树确定的某个点的奇偶性不同,显然就输出 IMPOSSIBLE

    接下来的构造说明了除了上述情况都是 POSSIBLE 的。

    构造一张图,这张图首先包含了原先的两棵树。

    加一个超级根,连接两棵树的树根。

    那么此时树上的每个点 (u)(X_u) 的奇偶性是与此时这个点的度数的奇偶性相同的。

    对于度数为奇数的点,在两棵树的对应节点之间连边。

    现在所有点都是偶数度数的了,而且整个图连通,那么就存在欧拉回路,给欧拉回路随便定个向。

    那么某个节点的 (X_i)(+1) 还是 (-1) 就取决于两棵树对应节点之间的连边的方向了。

    容易验证此时每个子树都满足条件。

    #include <cstdio>
    #include <vector>
    
    const int MN = 100005, ME = 300005;
    
    int N, A[MN], B[MN], R1, R2;
    
    int deg1[MN], deg2[MN];
    int E, eu[ME], ev[ME], dir[ME];
    std::vector<int> G[MN * 2];
    
    void DFS(int u) {
    	while (!G[u].empty()) {
    		int e = G[u].back();
    		G[u].pop_back();
    		if (!dir[e]) {
    			int v = eu[e] ^ ev[e] ^ u;
    			dir[e] = u == eu[e] ? 1 : 2;
    			DFS(v);
    		}
    	}
    }
    
    int Ans[MN];
    
    int main() {
    	scanf("%d", &N);
    	for (int i = 1; i <= N; ++i) scanf("%d", &A[i]);
    	for (int i = 1; i <= N; ++i) scanf("%d", &B[i]);
    	for (int i = 1; i <= N; ++i) {
    		if (A[i] != -1) {
    			++deg1[A[i]], ++deg1[i];
    			++E, eu[E] = A[i], ev[E] = i;
    			G[A[i]].push_back(E);
    			G[i].push_back(E);
    		} else R1 = i;
    		if (B[i] != -1) {
    			++deg2[B[i]], ++deg2[i];
    			++E, eu[E] = N + B[i], ev[E] = N + i;
    			G[N + B[i]].push_back(E);
    			G[N + i].push_back(E);
    		} else R2 = i;
    	}
    	++deg1[R1], ++deg2[R2];
    	++E, eu[E] = N + N + 1, ev[E] = R1;
    	G[N + N + 1].push_back(E), G[R1].push_back(E);
    	++E, eu[E] = N + N + 1, ev[E] = N + R2;
    	G[N + N + 1].push_back(E), G[N + R2].push_back(E);
    	for (int i = 1; i <= N; ++i) if ((deg1[i] ^ deg2[i]) & 1) return puts("IMPOSSIBLE"), 0;
    	puts("POSSIBLE");
    	for (int i = 1; i <= N; ++i) if (deg1[i] & 1) {
    		++E, eu[E] = i, ev[E] = N + i;
    		G[i].push_back(E), G[N + i].push_back(E);
    	}
    	DFS(N + N + 1);
    	for (int i = E; i > 2 * N; --i)
    		Ans[eu[i]] = dir[i] == 1 ? 1 : -1;
    	for (int i = 1; i <= N; ++i) printf("%d%c", Ans[i], " 
    "[i == N]);
    	return 0;
    }
    
  • 相关阅读:
    开源交易所源码
    ThinkCMF框架上的任意内容包含漏洞
    UAC绕过初探
    WinDbg常用命令系列---显示当前异常处理程序链!exchain
    WinDbg常用命令系列---错误消息显示!error
    WinDbg常用命令系列---检查符号X
    WinDbg常用命令系列---线程栈中局部上下文切换.frame
    WinDbg常用命令系列---显示数字格式化.formats
    WinDbg常用命令系列---清屏
    WinDbg常用命令系列---!address
  • 原文地址:https://www.cnblogs.com/PinkRabbit/p/AGC018.html
Copyright © 2011-2022 走看看