AtCoder Grand Contest 041 簡要題解


從這里開始

Problem A Table Tennis Training

  如果兩個人位置奇偶性相同,那么一定是兩個人同時往中間走。

  否則是兩個人走到邊上使得奇偶性相同,然后再像上面那樣做。

Code

#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;

#define ll long long

ll n, a, b;

int main() {
	scanf("%lld%lld%lld", &n, &a, &b);
	ll ans = -1;
	if (!((b - a) & 1)) {
		ans = (b - a) >> 1;
	} else {
		ans = min(a, n - b + 1) + ((b - a - 1) >> 1);
	}
	printf("%lld\n", ans);
	return 0;
}

Problem B Voting Judges

  不難發現,初試分數越大比越小的題目有更大的可能性可行。考慮二分答案,問題是怎么 check。

  條件相當於是要求嚴格比它的大的至多有 $P - 1$ 個。

  考慮先找最大的 $P - 1$ 個每個人都投。剩下的題目是讓盡可能多的人投。

  每次分配投的人的時候讓已經投的題數最少的人投,這樣至多有 2 種投的題數的人。

Code

#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;

const int N = 1e5 + 5;

int n, m, V, P;
int a[N];

boolean check(int mid) {
	int hig = V - 1, r1 = m, r2 = 0;
	int cnt = 0, r = n;
	for (r = n; r && cnt < P - 1; r--) {
		if (r == mid)
			continue;
		hig--, cnt++;
	}
	for (int i = 1; i <= r; i++) {
		if (i == mid)
			continue;
		if (a[i] > a[mid] + m)
			return false;
		int need = min(m, a[mid] + m - a[i]);
		if (r1 > need) {
			r1 -= need;
			r2 += need;
		} else {
			r1 = need - r1;
			r2 = m - r1;
			swap(r1, r2);
			hig--;
		}
	}
	return hig <= 0;
}

int main() {
	scanf("%d%d%d%d", &n, &m, &V, &P);
	for (int i = 1; i <= n; i++) {
		scanf("%d", a + i);
	}
	sort(a + 1, a + n + 1);
	int l = 1, r = n;
	while (l <= r) {
		int mid = (l + r) >> 1;
		if (check(mid)) {
			r = mid - 1;
		} else {
			l = mid + 1;
		}
	}
	printf("%d\n", n - r);
	return 0;
}

Problem C Domino Quality

  當 $n < 3$ 的時候顯然無解。

  手玩 $n = 3, 4, 5, 6, 7$,除了 $n = 3$ 的時候要求每行每列恰好有 1 個,剩下都要求有 3 個,然后剩下可以拿它們湊一下就行了。

Code

#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;

const int N = 1005;

char ans3[12][12] = {"aa.", "..a", "..a"};
char ans4[12][12] = {"aacd", 
					 "bbcd",
					 "dcaa",
					 "dcbb"};

char ans5[12][12] = {"aabc.", 
					 "..bcd", 
					 "iij.d", 
					 "n.jaa", 
					 "nkkbb"};

char ans7[12][12] = {"aade...", 
					 "bbde...", 
					 "ccffg..", 
					 "..i.gee", 
					 "..ihhdd", 
					 "....cba", 
					 "....cba"};

char ans10[12][12] = {"aacd......", 
					  "bbcd......", 
					  "..eeab....", 
					  "..ffab....", 
					  "ef..cc....", 
					  "ef..dd....", 
					  "......aacd", 
					  "......bbcd", 
					  "......dcaa", 
					  "......dcbb"};

int n;
char ans[N][N];

void put(char (*T)[12], int x, int d) {
	for (int i = 0; i < d; i++) {
		for (int j = 0; j < d; j++) {
			ans[x + i][x + j] = T[i][j];
		}
	}
}

int main() {
	scanf("%d", &n);
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			ans[i][j] = '.';
		}
	}
	if (n == 1 || n == 2) {
		puts("-1");
		return 0;
	} else if (!(n % 3)) {
		for (int i = 0; i < n; i += 3)
			put(ans3, i, 3);	
	} else if (n == 4) {
		put(ans4, 0, 4);
	} else if (n == 5) {
		put(ans5, 0, 5);
	} else if (n == 7) {
		put(ans7, 0, 7);
	} else if (n == 10) {
		put(ans10, 0, 10);
	} else if (n == 11) {
		put(ans4, 0, 4);
		put(ans7, 4, 7);
	} else {
		int c4 = 0, t = 0;
		while ((n - c4 * 4) % 5)
			c4++;
		for (int i = 0; i < c4; i++, t += 4)
			put(ans4, t, 4);
		while (t < n)
			put(ans5, t, 5), t += 5;
	}
	for (int i = 0; i < n; i++)
		puts(ans[i]);
	return 0;
}

Problem D Problem Scores

一. (材料分析題) 請根據下列材料回答問題

  材料1 神仙 jerome_wei 已經手持 2 枚國際金牌,而 yyf 有 0 塊。

  材料2 神仙 jerome_wei 在 csp-s 2019 上取得了全國前 10,四川第一的優異成績

  1. 神仙 jerome_wei 過了 D 后,跑來對 yyf 說:

 

   請簡要分析,這句話體現神仙 jerome_wei 怎樣的特點。

  2. 請簡要分析造成這樣現象的原因。


 

  條件可以轉化成長度為 $\left \lceil \frac{n - 1}{2} \right \rceil + 1$ 的前綴的和大於長度為 $\left \lceil \frac{n - 1}{2} \right \rceil$ 的后綴的和。

  枚舉中間的數的值 $x$,然后把所有數先填成這個值,然后左側可以減少一些值,右側可以加上一些值,它們的改變量之和小於 $x$。

  問題相當於將 $x$ 拆成不超過 某個接近 $n/2$ 的數 個不超過 $y$ 的數的方案數。 

  不考慮個數限制是個 trivial dp。因為這個數加一的和的兩倍比 $n - 1$ 大,所以可以枚舉有多少個數,把它們都減 1,這樣就能算不合法方案數。

Code

#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;

#define ll long long

int Mod;

typedef class Zi {
	public:
		int v;

		Zi() : v(0) {	}
		Zi(int v) : v(v) {	}
		Zi(ll x) : v(x % Mod) {	}

		friend Zi operator + (Zi a, Zi b) {
			return ((a.v += b.v) >= Mod) ? (a.v - Mod) : a; 
		}
		friend Zi operator - (Zi a, Zi b) {
			return ((a.v -= b.v) < 0) ? (a.v + Mod) : a; 
		}
		friend Zi operator * (Zi a, Zi b) {
			return 1ll * a.v * b.v;
		}
} Zi;

const int N = 5e3 + 5;

int n;
Zi f[N][N], g[N][N];

void work(Zi (*f)[N], int lim) {
	f[0][0] = 1;
	for (int i = 1; i <= n; i++) {
		for (int j = 0; j < i; j++)
			f[i][j] = f[i - 1][j];
		for (int j = i; j <= n; j++) {
			f[i][j] = f[i - 1][j] + f[i][j - i];
		}
	}
	for (int i = n; i; i--) {
		Zi sum = 0;
		for (int j = lim + 1; j <= n; j++) {
			sum = sum + f[i - 1][j - lim - 1];
			f[i][j] = f[i][j] - sum;
		}
	}
}

int main() {
	scanf("%d%d", &n, &Mod);
	int hn = (n + 1) >> 1;
	Zi ans = 0;
	if (n & 1) {
		work(f, hn - 1);
		work(g, hn - 1);
	} else {
		work(f, hn);
		work(g, hn - 1);
	}
	for (int i = 0; i <= n; i++) {
		for (int j = 1; j <= n; j++) {
			g[i][j] = g[i][j] + g[i][j - 1];
		}
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 0; j < i; j++) {
			ans = ans + f[i - 1][j] * g[n - i][i - j - 1];
		}
	}
	printf("%d\n", ans.v);
	return 0;
}

Problem E Balancing Network

  第一問大概就是考慮最終能到一根線的所有起始位置,如果它不是全集就無解,否則存在解。如果有一條在 $(x, y)$ 間的邊,最終到了 $x$,那么可能的起始位置是兩邊的並,求這個可以用 bitset 簡單搞一搞,求一下最后經過第 $i$ 條邊所有的起始位置,然后簡單構造一下。

  語文不太好,講不清楚,sad.....

  第二問就是你發現當 $n = 3$ 的時候一定有解,只考慮前三根線。假設當前在 $a, b$ 兩根線的最終會到不同線,並且下一條邊不是 $(a, b)$,考慮下下一條邊如果是 $(a, b)$,你就讓這一根線做一點調整就行了,顯然它仍然滿足這個限制。

  時間復雜度 $O(m)$

  另外簡單說一下神仙 jerome_wei 的做法。考慮建 $n$ 個點,表示初始在每個線,然后給每條邊建一個點。對於每條線向第一次遇到的邊連邊,每條邊向兩端下一個會到的邊連邊。建一個源點和匯點,源點向初始的 $n$ 個點連邊,如果某個點之后不會再遇到任何邊,那么向匯點連邊。

  問題可以轉化為求兩條除去起點終點外點不相交的路徑。

Code

#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;

#define pii pair<int, int>

const int N = 5e4 + 5;
const int M = 1e5 + 5;

int n, m, type;
char ans[M];
int us[M], vs[M];

namespace subtask1 {

	bitset<N> f[M];
	boolean vis[M];
	int h[N], A[M], B[M];

	void F(int p, int x) {
		if (!p || vis[p]) {
			return;
		}
		vis[p] = true;
		if (x == us[p]) {
			F(A[p], x);
			F(B[p], vs[p]);
		} else {
			ans[p] = 'v';
			F(B[p], x);
			F(A[p], us[p]);
		}
	}

	void solve() {
		for (int i = 1, u, v; i <= m; i++) {
			u = us[i], v = vs[i];
			if (!h[u]) {
				f[i].set(u);	
			} else {
				f[i] = f[h[u]]; 
			}
			swap(u, v);
			if (!h[u]) {
				f[i].set(u);	
			} else {
				f[i] |= f[h[u]]; 
			}
			swap(u, v);
			A[i] = h[u], B[i] = h[v];
			h[u] = h[v] = i;
		}
		for (int i = 1; i <= m; i++)
			ans[i] = '^';
		for (int i = 1; i <= m; i++) {
			if ((signed) f[i].count() == n) {
				F(i, us[i]);
				puts(ans + 1);
				return;
			}
		}
		puts("-1");
	}

}

namespace subtask2 {
	
	const int C = 451;

	set<pii> S;
	int h[C];
	int nxt[M][C];
	bitset<C> vis[M][2];

	boolean dfs(int p, int x, int y) {
		if (x == y) {
			return false;
		}
		if (p == m + 1) {
			return true;
		}
		int sx = -1, sy = -1;
		if (x == us[p] || y == us[p]) {
			sx = 0, sy = x ^ y ^ us[p];
		} else {
			sx = 1, sy = x ^ y ^ vs[p];
		}
		if (vis[p][sx].test(sy))
			return false;
		vis[p][sx].set(sy);

		// down
		int nx = (x == us[p]) ? vs[p] : x;
		int ny = (y == us[p]) ? vs[p] : y;
		if (dfs(min(nxt[p][nx], nxt[p][ny]), nx, ny)) {
			ans[p] = 'v';
			return true;
		}

		nx = (x == vs[p]) ? us[p] : x;
		ny = (y == vs[p]) ? us[p] : y;
		if (dfs(min(nxt[p][nx], nxt[p][ny]), nx, ny)) {
			ans[p] = '^';
			return true;
		}
		return false;
	}

	void solve() {
		for (int i = 1; i <= m; i++) {
			S.insert(pii(us[i], vs[i]));
		}
		if ((signed) S.size() < 1ll * n * (n - 1) / 2) {
			for (int i = 1; i <= n; i++) {
				for (int j = i + 1; j <= n; j++) {
					if (!S.count(pii(i, j))) {
						for (int k = 1; k <= m; k++) {
							if (us[k] == i || us[k] == j) {
								ans[k] = '^';
							} else {
								ans[k] = 'v';
							}
						}
						puts(ans + 1);
						return;
					}
				}
			}
			assert(false);
			return;
		}
		assert(n < C);
		for (int i = 1; i <= m; i++)
			ans[i] = 'v';
		for (int i = 1; i <= n; i++)
			h[i] = m + 1;
		for (int i = m; i; i--) {
			memcpy(nxt[i], h, sizeof(h));
			h[us[i]] = h[vs[i]] = i;
		}
		for (int i = 1; i <= n; i++) {
			for (int j = i + 1; j <= n; j++) {
				if (dfs(min(h[i], h[j]), i, j)) {
					puts(ans + 1);
					return;
				}
			}
		}
		puts("-1");
	}

}

int main() {
	scanf("%d%d%d", &n, &m, &type);
	for (int i = 1; i <= m; i++) {
		scanf("%d%d", us + i, vs + i);;
	}
	if (type == 1) {
		subtask1::solve();
	} else {
		subtask2::solve();
	}
	return 0;
}

Problem F Histogram Rooks

  平方比立方好想,心情簡單.jpg。

  考慮容斥,枚舉哪些位置一定沒有被覆蓋,剩下的位置任意放置或不放置。

  朴素 dp 是建笛卡爾樹,設 $f_{i, j, k}$ 表示考慮到第 $i$ 個點,硬點的位置一共占據了 $j$ 列,其中 $k$ 列已經填了。

  考慮一個點表示的矩形內的轉移,轉移有三種情況,一種是這一行什么都不干,一種是改變 $k$ 的值的情況下填數,一種是不會改變 $k$ 的轉一下填數。

  寫一下轉移式子不難發現,第二種轉移當且僅當 $k = 0$ 的時候轉移系數非 0。因此只有 $j = k$ 或者 $k = 0$ 的狀態有用。

  剩下的轉移只有背包合並,它的復雜度和樹上背包復雜度相同。

  所以總時間復雜度 $O(n^2)$

  三方做法好像是不容斥,考慮自底向上填,維護填上面的方案數,記錄有多少列必須填,一些可能要填的數量,可以發現其中某個數量為 0。

Code

#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;

#define ll long long

void exgcd(int a, int b, int& x, int& y) {
	if (!b) {
		x = 1, y = 0;
	} else {
		exgcd(b, a % b, y, x);
		y -= (a / b) * x;
	}
}

int inv(int a, int n) {
	int x, y;
	exgcd(a, n, x, y);
	return (x < 0) ? (x + n) : (x);
}

const int Mod = 998244353;

template <const int Mod = :: Mod>
class Z {
	public:
		int v;

		Z() : v(0) {	}
		Z(int x) : v(x){	}
		Z(ll x) : v(x % Mod) {	}

		friend Z operator + (const Z& a, const Z& b) {
			int x;
			return Z(((x = a.v + b.v) >= Mod) ? (x - Mod) : (x));
		}
		friend Z operator - (const Z& a, const Z& b) {
			int x;
			return Z(((x = a.v - b.v) < 0) ? (x + Mod) : (x));
		}
		friend Z operator * (const Z& a, const Z& b) {
			return Z(a.v * 1ll * b.v);
		}
		friend Z operator ~(const Z& a) {
			return inv(a.v, Mod);
		}
		friend Z operator - (const Z& a) {
			return Z(0) - a;
		}
		Z& operator += (Z b) {
			return *this = *this + b;
		}
		Z& operator -= (Z b) {
			return *this = *this - b;
		}
		Z& operator *= (Z b) {
			return *this = *this * b;
		}
		friend boolean operator == (const Z& a, const Z& b) {
			return a.v == b.v;
		} 
};

Z<> qpow(Z<> a, int p) {
	Z<> rt = Z<>(1), pa = a;
	for ( ; p; p >>= 1, pa = pa * pa) {
		if (p & 1) {
			rt = rt * pa;
		}
	}
	return rt;
}

typedef Z<> Zi;

const int N = 805;

int m, n;
int h[N];
Zi pw2[N * N];
Zi f[N][N][2];
Zi comb[N][N];
int L[N], R[N];

void prepare(int n) {
	comb[0][0] = 1;
	for (int i = 1; i <= n; i++) {
		comb[i][0] = comb[i][i] = 1;
		for (int j = 1; j < i; j++) {
			comb[i][j] = comb[i - 1][j] + comb[i - 1][j - 1];
		}
	}
	pw2[0] = 1;
	for (int i = 1; i <= n * n + 3; i++) {
		pw2[i] = pw2[i - 1] + pw2[i - 1];
	}
}

int build(int l, int r, int bh) {
	static Zi g[N][2];
	int p = ++n;
	L[p] = l, R[p] = r;
	int mi = h[l];
	for (int i = l; i <= r; i++) {
		mi = min(mi, h[i]);
	}
	int ls = l - 1;
	vector<int> son;
	int cnew = 0;
	for (int i = l; i <= r; i++) {
		if (h[i] == mi) {
			if (i - 1 >= ls + 1)
				son.push_back(build(ls + 1, i - 1, mi));
			ls = i, cnew++;
		}
	}
	if (ls < r)
		son.push_back(build(ls + 1, r, mi));
	for (int i = 0; i <= cnew; i++)
		f[p][i][i == 0] = comb[cnew][i];
	int lenp = cnew;
	for (auto e : son) {
		int lene = R[e] - L[e] + 1;
		for (int i = 0; i <= lenp + lene; i++)
			g[i][0] = g[i][1] = 0;
		for (int i = 0; i <= lenp; i++) {
			for (int j = 0; j <= lene; j++) {
				g[i + j][0] += f[p][i][0] * f[e][j][0];
				g[i + j][1] += f[p][i][1] * f[e][j][1];
			}
		}
		for (int i = 1; i <= lenp; i++) {
			g[i][0] += f[p][i][0] * f[e][0][1];
		}
		for (int i = 1; i <= lene; i++) {
			g[i][0] += f[e][i][0] * f[p][0][1];
		}
		lenp += lene;
		for (int i = 0; i <= lenp; i++)
			f[p][i][0] = g[i][0], f[p][i][1] = g[i][1];
	}
	int hei = mi - bh, len = r - l + 1;
	for (int t = 1; t <= hei; t++) {
		f[p][0][1] *= pw2[len];
		for (int i = 1; i <= len; i++) {
			Zi coef = (i & 1) ? (Mod - 1) : 1; 
			f[p][i][1] = (f[p][i][1] * (pw2[len - i] - 1) + f[p][i][0] * coef);
			f[p][i][0] *= pw2[len - i];
		}
	}
/*	cerr << p << " [" << l << ", " << r << "]:\n";
	for (int i = 0; i <= len; i++)
		cerr << f[p][i][0].v << " ";
	cerr << '\n';
	for (int i = 0; i <= len; i++)
		cerr << f[p][i][1].v << " ";
	cerr << '\n'; */
	return p;
}

int main() {
	scanf("%d", &m);
	for (int i = 1; i <= m; i++) {
		scanf("%d", h + i);
	}
	prepare(m);
	build(1, m, 0);
	Zi ans = 0;
	for (int i = 0; i <= m; i++)
		ans += f[1][i][1];
	printf("%d\n", ans.v);
	return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM