牛客周赛 Round 70题解

牛客周赛 Round 70题解

A 小苯晨跑

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

void solve() {
    int a[4];
    for (int i = 0; i < 4; i ++ ) cin >> a[i];
    sort(a, a + 4);
    if (a[0] == a[3]) {
        cout << "NO\n";
    } else {
        cout << "YES\n";
    }
}

int main() {
    int T; cin >> T;
    while (T -- ) solve();
    return 0;
}

B 小苯过马路

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

void solve() {
    int x, y, k, t;
    cin >> x >> y >> k >> t;
    char c; cin >> c;
    if (c == 'G') {
        if (t <= k) cout << t << endl;
        else cout << k + x + t << endl;
    } else {
        cout << k + t << endl;
    }
}

int main() {
    int T = 1;
    while (T -- ) solve();
    return 0;
}

C 小苯的字符串染色

由于可以选择长度为1的区间,所以直接把所有单个黑的区间染成白的即可。

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

void solve() {
    int n; cin >> n;
    string s; cin >> s;
    vector<int> a;
    for (int i = 0; i < n; i ++ ) {
        if (s[i] == '1') a.push_back(i + 1);
    }
    cout << a.size() << endl;
    for (auto c : a) cout << c << ' ' << c << endl;
}

int main() {
    int T = 1;
    cin >> T;
    while (T -- ) solve();
    return 0;
}

D 小苯的能量项链

题意:从左右两边开始去掉珠子,最多可以去k个,问最后剩下珠子中的左右端点权值和最大是多少。

考虑枚举最后剩下的珠子中的其中一个端点,这里以枚举右端点为例。

假如我们最后剩下的右端点是 \(i\) ,表示右边已经去掉了 \(n-i\) 个珠子,那么此时左边最多只能去掉 \(k-(n-i)\) 个珠子,记作 \(d\) 。那么最后的左端点只可能是 \([1, d+1]\) ,我们为了让这次枚举的左右端点和最大,需要保留左 \(d+1\) 个数的最大值,这里可以用前缀最大值来维护。

\(pre_i\) 表示前 \(i\) 个数的最大值,那么递推式子就是 \(pre_i=max(pre_{i-1},a_i)\)

记得处理下 \(k\) 过大的情况即可,时间复杂度 \(O(n)\)

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 5e5 + 10;

int a[N], pre[N];

void solve() {
    int n, k;
    cin >> n >> k;
    for (int i = 1; i <= n; i ++ ) {
        cin >> a[i];
        pre[i] = max(pre[i - 1], a[i]);
    }
    if (n == 1) {
        cout << a[1] << endl;
        return ;
    }
    int j = k + 1;
    int ans = 0;
    for (int i = n; i >= 2 && j >= 1; i -- ) {
        ans = max(ans, a[i] + pre[min(j, i - 1)]);
        j --;
    }
    cout << ans << endl;
}

int main() {
    int T = 1;
    cin >> T;
    while (T -- ) solve();
    return 0;
}

E 小苯的最短路

首先我们需要证明,在如题所示的完全图中,从 \(1\) 到任意一点 \(i\) 的最短路就是直接从 \(1\) 走到 \(i\) ,不绕路。接下来我们证明一下。

考虑反证法,假设存在一个点 \(i\) ,使得 \(1\)\(i\) 的最短路是需要绕路的,我们设绕的这个点是 \(j\) 。那么一定有 \(i \oplus 1 \gt 1 \oplus j + j \oplus i\) ,由于 \(a \oplus b \le a + b\) ,所以

\[i \oplus 1 = i \oplus j \oplus j \oplus 1 \le i \oplus j + j \oplus 1 \]

这与刚刚的式子矛盾,所以最短路就是直达,不用绕路。

知道了这个结论后,我们令 \(dis_i\)\(1\)\(i\) 的最短路,那么当输入为 \(n\) 的时候,我们只需要求 \(\bigoplus_{i=1}^n dis_i\) 即可,打表可以发现规律。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

void solve() {
    int n; cin >> n;
    if (n % 4 == 0) {
        cout << n << endl;
    } else if (n % 4 == 1) {
        cout << 0 << endl;
    } else if (n % 4 == 2) {
        cout << (n ^ 1) << endl;
    } else {
        cout << 1 << endl;
    }
}

int main() {
    int T = 1;
    cin >> T;
    while (T -- ) solve();
    return 0;
}

F 小苯的优雅好序列

题目需要让 \(a\) 数组中的每个连续子数组都是优雅的,容易看出其实我们只需要让所有长度为 \(2\) 的连续子数组优雅就可以了。

那么接下来思考如何根据给定的 \(a_i\)\(a_j\) 求出 \(x\)

我们不妨设 \(a_i \le a_j\) ,题目需要求出满足要求的 \((a_i + x) | (a_j + x)\) ,即存在一个正整数 \(k\) 满足

\[a_j+x = k (a_i+x) \]

\[a_j - a_i = (k-1)(a_i+x) \]

也就是 \((a_i + x) | (a_j - a_i)\)

所以我们只要枚举 \(a_j - a_i\) 的因子即可。最后的 \(x\) 其实就是用所有相邻两数之差 \(a_i - a_{i-1}\) 的因子求出的 \(x\) 的交集,所以我们先随便求一组差的解集,然后枚举所有的 \(x\) ,验证即可。

由于一个数 \(h\) 的因子数大概是 \(\log\) 级别的,所以最后的时间复杂度为 \(O(n \log a_i)\)

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
const int INF = 0x3f3f3f3f;

int a[N], n;

bool check(int x) {
	for (int i = 2; i <= n; i ++ ) {
		int val = abs(a[i] - a[i - 1]);
		if (val % (x + min(a[i], a[i - 1])) != 0) return false;
	}
	return true;
}

void solve() {
	int k;
	cin >> n >> k;
	for (int i = 1; i <= n; i ++ ) cin >> a[i];
	int pos = -1;
	for (int i = 2; i <= n; i ++ ) {
		if (a[i] == a[i - 1]) continue;
		if (pos == -1 || abs(a[i] - a[i - 1]) < abs(a[pos] - a[pos - 1]))
			pos = i;
	}
	if (pos == -1) {
		cout << k << ' ' << 1ll * (1 + k) * k / 2 << endl;
		return ;
	} 
	int val = abs(a[pos] - a[pos - 1]);
	vector<int> v;
	for (int i = 1; i * i <= val; i ++ ) {
		if (val % i) continue;
		int v1 = i - min(a[pos], a[pos - 1]);
		int v2 = val / i - min(a[pos], a[pos - 1]);
		if (v1 > 0 && v1 <= k) v.push_back(v1);
		if (v2 > 0 && v2 <= k) v.push_back(v2);
	}
	sort(v.begin(), v.end());
	v.erase(unique(v.begin(), v.end()), v.end());
	int cnt = 0;
	LL sum = 0;
	for (int x : v) {
		// cout << x << ' ';
		if (check(x)) {
			cnt ++;
			sum += x;
		}
	}
	cout << cnt << ' ' << sum << endl;
}

int main() {
#ifndef ONLINE_JUDGE
	freopen("1.in", "r", stdin);
	freopen("1.out", "w", stdout);
#endif
	int T; cin >> T;
	while (T -- ) solve();
	return 0;
}

G 小苯的树上操作

容易看出是换根dp,我们可以这样定义:

  • \(dp1_{u, 2}\) 表示以 \(u\) 为根的子树,\(u\) 不去掉的最大点权和
  • \(dp1_{u,0}\)\(dp1_{u,1}\) 分别表示在以 \(u\) 为根的子树中,只保留一颗子树的最大权值和次大权值(不包括 \(u\) 的权值)
  • \(dp2_{u,2}\) 表示以 \(u\) 为根,\(u\) 不去掉的最大点权和
  • \(dp2_{u, 0}\)\(dp2_{u,1}\) 分别表示在以 \(u\) 为根时,只保留一条相连的边的最大权值和次大权值(不包括 \(u\) 的权值)

接下来考虑转移

我们设当前树的根是 \(1\) ,遍历到 \(u\) 时,设 \(u\)\(m\) 个孩子,其中 \(u\) 的第 \(i\) 个孩子为 \(v_i\)

在求 \(dp1_{u,2}\) 时,考虑从某个孩子 \(v_i\) 转移过来,可以考虑保留 \(v_i\) 节点,从 \(dp1_{v_i,2}\) 转移过来;也可以不保留 \(v_i\) 节点,直接去掉整个子树,也就是 \(0\) ;还可以从 \(v_i\) 的最大子树转移过来(对应题目中的第二种操作),也就是 \(dp1_{v_i,0}\) 。以上这些取 \(\max\) 即可。

\[dp1_{u,2} = \sum_{i=1}^m \max(dp1_{v_i,2}, dp1_{v_i, 0}, 0) \]

在求 \(dp1_{u, 0}\)\(dp1_{u, 1}\) 的时候,也和刚刚一样,用 \(max(dp1_{v_i,2}, dp1_{v_i, 0}, 0)\) 来更新即可。

在求 \(dp2_{u, 2}\) 的时候,设 \(u\) 的父节点是 \(w\) , 首先默认 \(dp2_{u,2} = dp1_{u,2}\) ,然后考虑加上 \(w\) 相关的值。 如果带上 \(w\) 本身,那么值是 \(dp2_{w, 2}\) ,但是这其中可能会有 \(u\) 相关的值,也就是 \(\max(dp1_{u, 2}, dp1_{u, 0}, 0)\) ,需要减去;如果不带上 \(w\) ,那么值就是 \(dp2_{w, 0}\)

但是这个值有可能就是 \(u\) 的子树的权值和,如果发现是的话,就要用 \(dp2_{w, 1}\) 来更新,以上取一个 \(\max\) 后加上即可。

在求 \(dp2_{u, 0}\)\(dp2_{u, 1}\) 的时候,也和刚刚一样,用上面算出来的值更新即可。

时间复杂度 \(O(n)\)

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;

int a[N];
LL dp1[N][3], dp2[N][3];
vector<int> g[N];

void init(int n) {
	for (int i = 1; i <= n; i ++ ) {
		g[i].clear();
		for (int j = 0; j < 3; j ++ ) 
			dp1[i][j] = dp2[i][j] = 0;
	}
}
void dfs1(int u, int last) {
	dp1[u][2] = a[u];
	for (int v : g[u]) {
		if (v == last) continue;
		dfs1(v, u);
		LL val = max(dp1[v][2], dp1[v][0]);
		if (val > 0) dp1[u][2] += val;
		if (val > dp1[u][0]) {
			dp1[u][1] = dp1[u][0];
			dp1[u][0] = val;
		} else if (dp1[v][2] > dp1[u][1]) {
			dp1[u][1] = val;
		}
	}
}
void dfs2(int u, int last) {
	for (int v : g[u]) {
		if (v == last) continue;
		dp2[v][0] = dp1[v][0];
		dp2[v][1] = dp1[v][1];
		dp2[v][2] = dp1[v][2];
		LL val1 = dp2[u][2];
		LL tmp = max(dp1[v][0], dp1[v][2]);
		if (tmp > 0) val1 -= tmp;
		LL val2 = dp2[u][0];
		if (dp2[u][0] == tmp) val2 = dp2[u][1];
		LL val = max(val1, val2);
		dp2[v][2] += val;
		if (val > dp2[v][0]) {
			dp2[v][1] = dp2[v][0];
			dp2[v][0] = val;
		} else if (val > dp2[v][1]) {
			dp2[v][1] = val;
		}
		dfs2(v, u);
	}
}

void solve() {
	int n; cin >> n;
	init(n);
	for (int i = 1; i <= n; i ++ ) cin >> a[i];
	for (int i = 1; i < n; i ++ ) {
		int u, v;
		cin >> u >> v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	dfs1(1, 0);
	dp2[1][0] = dp1[1][0];
	dp2[1][1] = dp1[1][1];
	dp2[1][2] = dp1[1][2];
	dfs2(1, 0);
	LL ans = 0;
	for (int i = 1; i <= n; i ++ ) ans = max(ans, dp2[i][2]);
	cout << ans << endl;
}

int main() {
#ifndef ONLINE_JUDGE
	freopen("1.in", "r", stdin);
	freopen("1.out", "w", stdout);
#endif
	int T; cin >> T;
	while (T -- ) solve();
	return 0;
}
posted @ 2024-12-03 10:11  Time_Limit_Exceeded  阅读(21)  评论(0编辑  收藏  举报