ARC121 补题笔记

A:注意到第二大的要么是 \(|x_i - x_j|\) 非常大,要么是 \(|y_i - y_j|\) 非常大。我们将 \(x_i, y_i\) 分别为关键字排序,然后去最大的和最小的几个判重一下即可。

B:注意到每一项单拎出来匹配都不会出现 \(2\) 个以上,那就直接 dp 子序列就可以了。

C:一个贪心的策略就是类似于冒泡排序,但是我们需要调整第一步,有一种很好写的做法是先不管前 \(4\) 个的顺序,暴力把最后 \(n-4\) 个换到位,最后 \(4\) 个微调一下即可。

D:有一个很妙的点,选 \(1\) 个可以看做加一个 \(0\) 然后选两个。所以只需要考虑选两个的情况就行了。

假如所有数全是偶数,且只能选两个,让极差最小,可以证明一定是头尾配对。这样就可以在 \(O(n^2)\) 的时间内解决此题。

证明:设有 \(A \leq B \leq C \leq D\),那么上述策略的消耗 \(\max = \max(A + D, B + C)\),其他策略的 \(\max = \max(A + C, B + D)\).

由于左式中的每一项都小于等于右式中的某一项,所以左式 \(\leq\) 右式。

换成 \(\min\),由于左式中的每一项都大于等于右式中的某一项,所以 \(\min\)\(\geq\) 关系。两边都会让极差缩小,所以证毕。

很妙的一个题,我没想到 1 个可以配一个 0 变成 2 个就 gg 了。。。

#include <bits/stdc++.h>
#define rep(i,l,r) for(int i = (l); i <= (r); ++i)
#define per(i,r,l) for(int i = (r); i >= (l); --i)
#define int long long
using namespace std;
typedef long long ll;
inline int gi() {
	int f = 1, x = 0; char ch = getchar();
	while (ch < '0' || ch > '9') {if (ch == '-') f = -f;ch = getchar();}
	while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
	return f * x;
}
const int N = 10050; 
int n, a[N];
signed main(){
	n = gi();
	rep (i, 1, n) a[i] = gi();
	int ans = LLONG_MAX;
	for (int i = 0, j = n; i <= j; ++i){
		if (n % 2) {
			a[++n] = 0;
			continue;
		}
		sort (a + 1, a + 1 + n);
		int mx = LLONG_MIN, mn = LLONG_MAX;
		for (int k = 1; k <= n - k + 1; ++k) {
			mx = max(mx, a[k] + a[n - k + 1]);
			mn = min(mn, a[k] + a[n - k + 1]);
		}
		ans = min(ans, mx - mn);
		a[++n] = 0;
	}
	cout << ans << '\n';
	return 0;
}

E:当时最后 10 min 开始想这题,觉得临门一脚了,可惜最后由于时间原因没过。

看到题目,发现是“限制 \(a_i\) 不能为 \(i\) 的祖先”,我们考虑对 \(a_i\) 的置换计数,这样就是“限制 \(a_i\) 不能为 \(i\) 的子树”,好搞许多,原因是这个限制是对区间的限制。

现在转化成一个问题:一个排列,每个位置有个值域,求排列方案数。这个东西 EI 讲过是没有多项式做法的,考虑有没有特殊性质。

之前做过一个题是值域递增的,这种情况下我们可以知道在每一个位置选的方案数。这个题是否能够建立这样一种包含关系从而做完呢?其实是可以的。。。

你发现树本身的子树包含关系就很好的描述了这一点,那就做完了。。。容斥的复杂度是 \(O(n^2)\) 的。

#include <bits/stdc++.h>
#define rep(i,l,r) for(int i = (l); i <= (r); ++i)
#define per(i,r,l) for(int i = (r); i >= (l); --i)
using namespace std;
typedef long long ll;
inline int gi() {
	int f = 1, x = 0; char ch = getchar();
	while (ch < '0' || ch > '9') {if (ch == '-') f = -f;ch = getchar();}
	while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
	return f * x;
}

const int N = 2005, mod = 998244353;
int n, p[N], sz[N];
vector<int> g[N];
int dp[N][N], temp[N];

void mg (int u, int v) {
	memcpy (temp, dp[u], sizeof (dp[u]));
	memset (dp[u], 0, sizeof (dp[u]));
	for (int i = 0; i <= sz[u]; ++i)
		for (int j = 0; j <= sz[v]; ++j)
			(dp[u][i + j] += (ll)temp[i] * dp[v][j] % mod) %= mod;
}
void dfs (int u, int par = 0) {
	dp[u][0] = 1;
	sz[u] = 1;
	for (int i = 0; i < g[u].size(); ++i) {
		int v = g[u][i];
		if (v == par) continue;
		dfs (v, u);
		mg (u, v);
		sz[u] += sz[v];
	}
	memcpy (temp, dp[u], sizeof (dp[u]));
	for (int e = 0; e < sz[u]; ++e)
		(dp[u][e + 1] += (ll)temp[e] * (sz[u] - 1 - e) % mod) %= mod;
}
int main(){
	scanf ("%d", &n);
	for (int i = 2; i <= n; ++i) {
		int p = gi();
		g[p].push_back (i);
	}
	dfs (1, 0);
	int ans = 0;
	vector<int> fac(n + 1); fac[0] = 1;
	rep (i, 1, n) fac[i] = (ll)fac[i - 1] * i % mod;
	for (int s = 0; s <= n; ++s) {
		if (s & 1) ans = (ans - (ll)dp[1][s] * fac[n - s] % mod + mod) % mod;
		else (ans += (ll)dp[1][s] * fac[n - s] % mod) %= mod;
	}
	cout << ans << '\n';
	return 0;
}

F:有一步重要的观察,就是我们应该如何进行操作。先进行 AND 操作后进行 OR 操作看起来是很显然的。

OR 起来为 1 不是很好求,考虑容斥为 OR 起来为 \(0\),这意味着每个由 AND 构成的连通块内至少都有一个 \(0\).

现在问题转化成:你要选择一些边割掉,使得每个连通块内至少有一个 \(0\),求方案数。简单树形 dp 即可。

为什么我当时不会这题啊啊啊啊啊啊啊

#include <bits/stdc++.h>
#define rep(i,l,r) for(int i = (l); i <= (r); ++i)
#define per(i,r,l) for(int i = (r); i >= (l); --i)
using namespace std;
typedef long long ll;
inline int gi() {
	int f = 1, x = 0; char ch = getchar();
	while (ch < '0' || ch > '9') {if (ch == '-') f = -f;ch = getchar();}
	while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
	return f * x;
}
const int N = 100100, mod = 998244353;
inline int add (int a, int b) {
	return ((a += b) >= mod ? a - mod : a);
} inline int sub (int a, int b) {
	return ((a -= b) < 0 ? a + mod : a);
} inline int fpw (int a, int b) {
	int res = 1; while (b) {
		if (b & 1) res = (ll)res * a % mod;
		a = (ll)a * a % mod, b >>= 1;
	}
	return res;
}
vector<int> g[N];
int dp[N][2];
int temp[2];
void merge (int u, int v) {
	memcpy (temp, dp[u], sizeof (dp[u]));
	memset (dp[u], 0, sizeof (dp[u]));
	
	for (int i = 0; i < 2; ++i)
		for (int j = 0; j < 2; ++j) {
			if (!j) dp[u][i] = add(dp[u][i], (ll)temp[i] * dp[v][j] % mod);
			dp[u][i & j] = add(dp[u][i & j], (ll)temp[i] * dp[v][j] % mod);
		}
}
void dfs (int u, int par = 0) {
	dp[u][0] = dp[u][1] = 1;
	for (int i = 0; i < g[u].size(); ++i) {
		int v = g[u][i];
		if (v == par) continue;
		dfs (v, u);
		merge (u, v);
	}
}
int n;
int main(){
	n = gi();
	rep (i, 1, n - 1) {
		int a = gi(), b = gi();
		g[a].push_back (b);
		g[b].push_back (a);
	}
	
	dfs (1, 0);
	cout << sub(fpw(2, 2 * n - 1), dp[1][0]) << '\n';
	return 0;
}
posted @ 2021-05-29 23:06  LiM_233  阅读(80)  评论(0)    收藏  举报