AtCoder Grand Contest 035 簡要題解

從這裏開始

Problem A XOR Circle

  你發現,權值的循環節爲 $a_0, a_1, a_0\oplus a_1$,然後暴力即可。

Code

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

const int N = 1e5 + 5;

int n;
int a[N], b[N];

void quitif(boolean condition, const char* vert = "No") {
	if (condition) {
		puts(vert);
		exit(0);
	}
}

boolean check(int x, int y) {
	b[0] = x, b[1] = y;
	for (int i = 2; i < n; i++)
		b[i] = b[i - 1] ^ b[i - 2];
	if (b[n - 2] ^ b[n - 1] ^ x)
		return false;
	if (b[n - 1] ^ b[0] ^ y)
		return false;
	sort(b, b + n);
	for (int i = 0; i < n; i++)
		if (b[i] ^ a[i])
			return false;
	return true;
}

int main() {
	scanf("%d", &n);
	for (int i = 0; i < n; i++) {
		scanf("%d", a + i);
	}
	sort(a, a + n);
	int cnt = 1;
	for (int i = 1; i < n; i++) {
		cnt += (a[i] != a[i - 1]);
	}
	quitif(cnt > 3);
	if (cnt == 1) {
		if (check(0, 0)) {
			puts("Yes");
		} else {
			puts("No");
		}
	} else if (cnt == 2) {
		if (check(a[0], a[n - 1])) {
			puts("Yes");
		} else {
			puts("No");
		}
	} else {
		vector<int> qaq {a[0]};
		for (int i = 1; i < n; i++) {
			if (a[i] ^ a[i - 1]) {
				qaq.push_back(a[i]);
			}
		}
		if (check(qaq[0], qaq[1]) || check(qaq[0], qaq[2]) || check(qaq[1], qaq[2])) {
			puts("Yes");
		} else {
			puts("No");
		}
	}
	return 0;
}

Problem B Even Degrees

  如果邊數是奇數,顯然無解。

  如果是一棵樹,那麼從葉節點開始構造,每次選擇當前點到父節點的那條邊,使得子樹內所有點的出度都是偶數。不是樹的話,非樹邊隨便選。

Code

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

const int N = 1e5 + 5;

int n, m;
int uf[N];
int d[N];
vector<int> G[N];

int find(int x) {
	return uf[x] == x ? x : (uf[x] = find(uf[x]));
}

void dfs(int p, int fa) {
	for (auto e : G[p]) {
		if (e ^ fa) {
			dfs(e, p);
		}
	}
	if (!fa)
		return;
	if (d[p]) {
		d[p] ^= 1;
		printf("%d %d\n", p, fa);
	} else {
		d[fa] ^= 1;
		printf("%d %d\n", fa, p);
	}
}

int main() {
	scanf("%d%d", &n, &m);
	if (m & 1) {
		puts("-1");
		return 0;
	}
	for (int i = 1; i <= n; i++)
		uf[i] = i;
	for (int i = 1, u, v; i <= m; i++) {
		scanf("%d%d", &u, &v);
		if (find(u) ^ find(v)) {
			uf[find(u)] = find(v);
			G[u].push_back(v);
			G[v].push_back(u);
		} else {
			printf("%d %d\n", u, v);
			d[u] ^= 1;
		}
	}
	dfs(1, 0);
	assert(!d[1]);
	return 0;
}

Problem C Skolem XOR Tree

  講一個垃圾做法。

  如果 $n = 2^k\ (k\geqslant 0)$,顯然無解。

  如果 $n$ 爲奇數,那麼首先可以造出 $1 - 2 - 3 - 1' - 2' - 3'$。然後 $i - (i +1) - [i \oplus (i + 1)]- i' - (i + 1)' \ (2 | i)$。因爲它們最高位相同,所以 $i \oplus (i + 1)$ 總是小於 $i$。

  如果 $n$ 爲偶數,設 $2^k < n < 2^{k + 1}$, 設 $n$ 的lowbit 爲 $x$,考慮 $i \oplus (i - 1) < 2^k$,因爲 $k > 1$,所以 $1 \oplus 2 \oplus \cdots \oplus (2^k - 1) = 0$,可以把它們按一定順序排列來構造。把 $n,  n - 1, x, x - 1$ 按某種順序排列可以輕鬆構造出 $n - x$,這樣大於等於 $2^k$ 的數還有偶數個沒有加到樹上,把它們兩兩配對,然後按奇數的後半部分構造就可以了。

Code

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

const int N = 1e5 + 5;

int n;

void O(int x, int y) {
	printf("%d %d\n", x, y);
}

int main() {
	scanf("%d", &n);
	if (!(n - (n & (-n)))) {
		puts("No");
		return 0;
	}
	puts("Yes");
	if (n & 1) {
		O(1, 2);
		O(2, 3);
		O(3, 1 + n);
		O(1 + n, 2 + n);
		O(2 + n, 3 + n);
		for (int i = 4; i < n; i += 2) {
			int v = i ^ (i + 1);
			O(i, i + 1);
			O(i + 1, v);
			O(v, i + n);
			O(i + n, i + n + 1);
		}
	} else {
		int x = (n & (-n)) - 1;
		int y = x + 1;
		assert(!(x ^ y ^ (n - 1) ^ n));
		O(y, x);
		O(x, n - 1);
		O(n - 1, n);
		O(n, y + n);
		O(y + n, x + n);
		O(x + n, n + n - 1);
		O(n + n - 1, n + n);
		int hig = 1;
		while ((hig << 1) < n)
			hig <<= 1;
		vector<int> a;
		for (int i = 1; i < hig; i++) {
			a.push_back(i);
		}
		a.erase(find(a.begin(), a.end(), x));
		a.erase(find(a.begin(), a.end(), y));
		for (int i = 1; i < (signed) a.size(); i++)
			O(a[i - 1], a[i]);
		O(a.back(), x);
		O(y, a[0] + n);
		for (int i = 1; i < (signed) a.size(); i++)
			O(a[i - 1] + n, a[i] + n);
		
		a.clear();
		int z = x ^ (n - 1);
		O(z, x);
		O(n - 1, z + n);
		for (int i = hig; i < n - 1; i++)
			a.push_back(i);
		a.erase(find(a.begin(), a.end(), z));
		assert(!(a.size() & 1));
		for (int i = 1; i < (signed) a.size(); i += 2) {
			int v = a[i - 1] ^ a[i];
			O(a[i - 1], a[i]);
			O(a[i], v);
			O(v, a[i - 1] + n);
			O(a[i - 1] + n, a[i] + n);
		}
	}
	return 0;
} 

Problem D Add and Remove

  不難發現,一個數的貢獻係數並不多。

  設 $f_{l, r, cl, cr}$ 表示,消除 $l, r$ 中的數,對左邊的貢獻爲 $cl$ ,對右邊的貢獻爲 $cr$。

  轉移考慮枚舉最後刪掉的數,那麼它左側的貢獻係數變爲 $(cl, cl + cr)$,右側的貢獻係數爲 $cl + cr, cr)$。

  粗略分析可得一個上界 $O(n^3 2^n)$。

Code

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

typedef class Status {
	public:
		int l, r, cl, cr;

		Status() {	}
		Status(int l, int r, int cl, int cr) : l(l), r(r), cl(cl), cr(cr) {	}

		boolean operator < (Status b) const {
			if (l ^ b.l)
				return l < b.l;
			if (r ^ b.r)
				return r < b.r;
			if (cl ^ b.cl)
				return cl < b.cl;
			return cr < b.cr;
		}
} Status;

#define ll long long

int n;
int a[20];
map<Status, ll> F;

ll dp(int l, int r, int cl, int cr) {
	if (l > r)
		return 0;
	if (l == r)
		return 1ll * a[l] * (cl + cr);
	Status scur (l, r, cl, cr);
	if (F.count(scur))
		return F[scur];
	ll rt = 1e18;
	for (int k = l; k <= r; k++) {
		ll cost = a[k] * 1ll * (cl + cr);
		cost += dp(l, k - 1, cl, cl + cr);
		cost += dp(k + 1, r, cl + cr, cr);
		rt = min(rt, cost);
	}
	return F[scur] = rt;
}

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) {
		scanf("%d", a + i);
	}
	if (n == 2) {
		printf("%d", a[1] + a[2]);
		return 0;
	}
	ll ans = dp(2, n - 1, 1, 1) + a[1] + a[n];
	printf("%lld\n", ans);
	return 0;
}

Problem E Develop

  考慮 $x$ 向 $x - 2$ 以及 $x + K$ 連一條有向邊。存在方案當且僅當不存在環。充分性考慮不存在環意味着存在拓撲序,按拓撲序操作即可。必要性考慮如果存在環,並且能夠操作,那麼考慮在這個環上的最後一次操作會使得環上出現一個數,這會和最後一次操作矛盾。

  如果 $K$ 是偶數,那麼奇數位和偶數位是獨立的,不存在環當且僅當在奇數位置或偶數位置中沒有不選長度超過 $K / 2$ 的段。

  如果 $K$ 是奇數,不難發現如果存在環,一定存在大小爲 $K + 2$ 的環。考慮把所有數分成兩列,第一列是所有奇數,第二列是所有偶數,如果存在從左邊向下和向右的長度爲 $K + 2$ 的路徑,那麼一定存在長度爲 $K + 2$ 的環,考慮左邊從 $x$ 走到了 $x - 2l$,然後走到了 $x - 2l + K$,然後走到 $x - 2l + K - 2(K - l) = x - K$ 此時它加上 $K$ 等於 $x$。

  考慮讓偶數 $x$ 和 $x - K$ 對齊,設 $f_{i, j, k}$ 表示考慮前 $i$ 行,右邊連續不選了 $j$ 個數,從左側最後一個點開始只能下或向右走的最長的長度爲 $k$。轉移討論一下兩邊選不選。

  時間複雜度 $O(n^3)$

Code

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

const int N = 155;

#define ll long long

int n, K, K2, Mod;

template <typename T>
void fix(T& x) {
	(x >= Mod) && (x -= Mod);
}

namespace subtask1 {
	
	int f[N][N];
	void solve() {
		int lim = (n + 1) >> 1;
		f[0][0] = 1;
		for (int i = 1; i <= lim; i++) {
			for (int j = 0; j < i && j <= K2; j++) {
				fix(f[i][j + 1] += f[i - 1][j]);
				fix(f[i][0] += f[i - 1][j]);
			}
		}
		int ans0 = 0, ans1 = 0;
		for (int i = 0; i <= K2; i++)
			fix(ans0 += f[n >> 1][i]);
		for (int i = 0; i <= K2; i++)
			fix(ans1 += f[lim][i]);
		ans0 = 1ll * ans0 * ans1 % Mod;
		printf("%d\n", ans0);
	}

}

namespace subtask2 {
	
	int f[N][N][N];
	void solve() {
		f[0][0][0] = 1;
		for (int i = 1; i <= n + 1; i++) {
			int r = i << 1, l = r - K;
			boolean have_r = (r >= 1 && r <= n), have_l = (l >= 1 && l <= n);
			for (int j = 0; j < i; j++) {
				for (int k = 0; k <= K + 1; k++) {
					int v = f[i - 1][j][k];
					if (!v)
						continue;
#define F(nj, nk) ((nk) <= K + 1) && (fix(f[i][nj][(nk)] += v), 0)
					fix(f[i][0][0] += v);
					if (have_r)
						fix(f[i][j + 1][0] += v);
					if (have_l)
						F(0, (k ? k + 1 : 0));
					if (have_l && have_r)
						F(j + 1, max(j + 2, k + 1));
				}
			}
		}
		printf("%d\n", f[n + 1][0][0]);
	}

}

int main() {
	scanf("%d%d%d", &n, &K, &Mod);
	K2 = K >> 1;
	if (K & 1) {
		subtask2::solve();
	} else {
		subtask1::solve();
	}
	return 0;
}

Problem F Two Histograms

  定義一個局面是基本局面當且僅當不存在 $(i, j)$,使得 $k_i + 1 = j, l_j = i$,出現這樣的情況可以變成 $k_i + 1, l_j - 1$。不難證明對於每種局面可以轉化到至少一個基本局面。

  可以發現,任意兩個基本局面都是不同的。

  假設存在連兩個基本局面 $(k_1, k_2, \cdots, k_n, l_1, \cdots, l_m)$ 和 $(k'_1, \cdots, k'_2, l'_1, \cdots, l'_m)$ 相同。設 $j$ 是最小的使得 $l_j \neq l'_j$ 的整數,不妨設 $l_j < l'_j$。因爲它們產生的網格相同,那麼有:

  • 如果 $j = 1$,那麼有 $A_{l'_1, 1} = 1$,那麼 $k'_{l'_1} + 1 = 1$,矛盾。
  • 如果 $j > 1$,那麼有 $A_{l'_j, j} = 1$,那麼有 $k_{l'_j} < j, k_{l'_j} \geqslant j$,如果 $k_{l'_j} = j - 1$,那麼矛盾,否則 $A_{l'_j, j - 1}$ 不可能相等,矛盾。

  因此只用對基礎局面進行計數,這個容斥即可。

Code

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

const int N = 5e5 + 5;

#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;

int n, m;
Zi fac[N], _fac[N];

void init_fac(int n) {
	fac[0] = 1;
	for (int i = 1; i <= n; i++) {
		fac[i] = fac[i - 1] * i;
	}
	_fac[n] = ~fac[n];
	for (int i = n; i; i--) {
		_fac[i - 1] = _fac[i] * i; 
	}
}
Zi comb(int n, int m) {
	return fac[n] * _fac[m] * _fac[n - m];
}

Zi pwn[N], pwm[N];
int main() {
	scanf("%d%d", &n, &m);
	init_fac(max(n, m));
	pwn[0] = 1;
	for (int i = 1; i <= m; i++)
		pwn[i] = pwn[i - 1] * (n + 1);
	pwm[0] = 1;
	for (int i = 1; i <= n; i++)
		pwm[i] = pwm[i - 1] * (m + 1);
	Zi ans = 0;
	for (int i = 0; i <= n && i <= m; i++) {
		Zi tmp = comb(n, i) * comb(m, i) * fac[i] * pwn[m - i] * pwm[n - i];
		if (i & 1) {
			ans -= tmp;
		} else {
			ans += tmp;
		}
	}
	printf("%d\n", ans.v);
	return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章