Codeforces Round 827 (Div. 4)C~G

Codeforces Round 835 (Div. 4)D~G

D. Challenging Valleys(模拟)

题意

给一个数组,判断一下这个数字是否只存在一个连续子数组满足那些条件

思路

直接去枚举,时间是线性的,不怕超

枚举左端点l,然后右端点r如何确定?如果遇到了a[l] != a[r],那么r-1就是右端点,因为如果要满足条件,那么子数组的所有数值都必须英语

枚举出来子数组之后就去判断后面两个条件

if ((l == 0 || a[l - 1] > a[l]) && (r == n - 1 || a[r] < a[r + 1]))cnt++;

如果不符合呢,就让右端点l跳到r+1,继续去枚举

如果最后cnt等于1,就合法(我写了个特判数组长度为1的时候,应该可以不要)

关键是要写出健壮通用一点的代码, 要不然有很多边界情况要去讨论很麻烦

代码

#include<bits/stdc++.h>
#define endl '\n'
using namespace std;

void solve() {
	int n; cin >> n;
	vector<int>a(n);
	for (int i = 0; i < n; i++)cin >> a[i];

	int l = 0, r = 0, cnt = 0;

	for (int i = 0, j = 0; i < n; i++) {
		j = i;
		while (j < n && a[i] == a[j]) {
			j++;
		}
		l = i; r = j - 1;
		if ((l == 0 || a[l - 1] > a[l]) && (r == n - 1 || a[r] < a[r + 1]))cnt++;
		i = j - 1;
	}

	if (n == 1)cnt = 1;
	if (cnt == 1)cout << "YES" << endl;
	else cout << "NO" << endl;
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int _; _ = 1;
	cin >> _;
	while (_--)	solve();
	return 0;
}

E. Binary Inversions(前缀和)

题意

给一个只包含1/0的数组,然后最多可以操作1次(翻转某个位置的1/0)。使得数组里面逆序对最大是多少

思路

搞两个数组cnt_0和cnt_1分别表示某个位置(包含自己)前面有多少个0或者1

然后枚举每个位置(每个位置都尝试翻转一下,看看得到的答案是多少)

代码

#include<bits/stdc++.h>
#define endl '\n'
using namespace std;
vector<long long>ans;

void solve() {
	int n; cin >> n;
	vector<int>a(n + 1), cnt_0(n + 1), cnt_1(n + 1);
	long long sum = 0;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		cnt_1[i] = cnt_1[i - 1];
		cnt_0[i] = cnt_0[i - 1];
		if (a[i]) {
			cnt_1[i]++;
		}
		else {
			cnt_0[i]++;
			sum += cnt_1[i];
		}
	}
//	cout << "sum:" << sum << endl;
	long long maxSum = sum;

	// 如果把0换成1,那就要看前面1的数量和后面0的数量
	//					cnt_1[i] ->	cnt_0[n]-cnt_0[i]
	// 
	// 如果把1换成0,那也要看前面1的数量和后面0的数量
	//					cnt_1[i] <-	cnt_0[n]-cnt_0[i]
	for (int i = 1; i <= n; i++) {
		long long pre = cnt_1[i-1];
		long long back = cnt_0[n] - cnt_0[i];
		if (a[i]) {
			maxSum = max(maxSum, sum + (pre - back));
		}
		else {
			maxSum = max(maxSum, sum + (back - pre));
		}
	}
	ans.push_back(maxSum);
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int _; _ = 1;
	cin >> _;
	while (_--)	solve();
	for (auto x : ans)cout << x << endl;
	return 0;
}

F. Quests(二分、前缀和、模拟)

题意

每天只能做一个任务,然后得到金币,需要判断d天能能不能搞到c个金币。

可以设置一个k,做完某个任务之后,只有隔k天之后才能再做这个任务。问k最大是多少。

思路

Infinity

这时候肯定就是不管你k多大,我把所有任务(优先做金币多的)做完。根本不需要重复做任务,就可以得到c个金币,那说明k随便你多大,都能满足。所以是Infinity

Impossible

如果根本没有限制间隔时间,你就在d天内一直调金币最多的那个任务去做,做满d天,都不够c个金币,这时候就是Impossible


因为k越大,搞到的金币肯定越少。所以可以二分k。这里关键就是check函数怎么写。

k的意思是,做完一个任务之后,间隔k天,才可以重新做

我们做任务肯定是优先挑金币多的去做,假设我们的d挺大的,我们的任务可以做好几轮。那么k+1就是一个轮回,在做完最多金币的任务之后,第二天肯定做次多金币的任务。在过了k天之后,肯定是赶紧再去做金币最多的任务,然后又是次多的那个任务。

所以k+1是一个周期,我们在d天内可以有 cycle = d / (k+1)个周期

然后会多出来remain = d % (k+1)天

另外就是计算金币的话不需要一天一天的去加,前面也说了,每次做任务肯定挑当前能做的,而且金币数量最多的去做。所以可以对金币的数组排序,再求前缀和。每个周期获得的金币自然就是sum[k+1](当然,如果周期长度大于任务的数量,那么我们做任务会有一段真空期,这段时间没有任务可以做)

代码

#include<bits/stdc++.h>
#define endl '\n'
using namespace std;
vector<long long>ans;
int n; long long c, d, maxA;
vector<int>a;
vector<long long>sumA;

bool check(int k) {
	// 一个周期可以做k+1个任务
	int cycle = d / (k + 1);
	// 还会剩几天
	int remain = d % (k + 1);

	long long sum = 0;
	sum += sumA[min(k + 1, n)] * cycle;
	sum += sumA[min(remain, n)];
	return sum >= c;
}

bool cmp(int& a, int& b) {
	return a > b;
}

void solve() {
	cin >> n >> c >> d;
	a.resize(n + 1);
	sumA.assign(n + 1, 0);
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
	}
	sort(a.begin() + 1, a.end(), [&](int a, int b) {return a > b; });
	for (int i = 1; i <= n; i++) {
		sumA[i] = sumA[i - 1] + a[i];
	}

	long long sum = 0;
	// d天最多多少,每天都不一样
	for (int i = 1; i <= min((int)d, n); i++) {
		sum += a[i];
	}
	if (sum >= c) {
		cout << "Infinity" << endl;
		return;
	}

	maxA = a[1];
	if (maxA * d < c) {
		cout << "Impossible" << endl;
		return;
	}

	int l = 0, r = 2e5;
	int k = 0, mid;
	while (l <= r) {
		mid = (l + r) >> 1;
		if (check(mid)) {
			k = mid;
			l = mid + 1;
		}
		else {
			r = mid - 1;
		}
	}
	cout << k << endl;
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int _; _ = 1;
	cin >> _;
	while (_--)	solve();
	for (auto x : ans)cout << x << endl;
	return 0;
}

G. SlavicG's Favorite Problem(DFS,树)

题意

给一颗树,点a和b,点a每次移动都会让自己的状态异或上边权,如果想要到达b点,那么到达b点的前一步,必须能够使得这时候的状态异或上去b那条边之后变成0(也就是到达b之前,状态必须和去b的那条边权一样,异或的结果才可能为0)。并且可以传送,但是不能直接传到b点

思路

因为传送的存在,所以可以让a出发,到所有点,记录所有能变成的状态。再从b出发,同样到所有点,也记录所有状态。如果发现这两堆状态,有相同的状态i。**a 异或得到了 i,b 异或得到了 i。让a先到 i 状态,然后传送到b异或成 i 的那个点,就可以到达b了。 **

由于树是连通的,而且没有环,所以a可以深搜到每一个点,得到每一个点的状态。b也同样会深搜到所有点。并且由于没有环,从同一个点出发的时候,每个点的状态也是唯一的,不会重复到达。

代码

#include<bits/stdc++.h>
#define endl '\n'
using namespace std;
vector<long long>ans;
const int N = 1e5 + 5;
int n, m, a, b, cnt;
int head[N], state[N];
struct edge {
	int to, nex, w;
}e[2 * N];

void add(int x, int y, int w) {
	cnt++;
	e[cnt] = { y,head[x],w };
	head[x] = cnt;
}

void dfs(int u, int par) {
	for (int j = head[u]; j != -1; j = e[j].nex) {
		int v = e[j].to;
		if (v == par || v == b)continue;
		state[v] = state[u] ^ e[j].w;
		dfs(v, u);
	}
}

void solve() {
	cin >> n >> a >> b;
	cnt = 0;
	memset(e, 0, sizeof(e));
	memset(head, -1, sizeof(head));
	memset(state, -1, sizeof(state));
	for (int i = 0, u, v, w; i < n - 1; i++) {
		cin >> u >> v >> w;
		add(u, v, w);
		add(v, u, w);
	}

	state[a] = 0;
	dfs(a, a);

	set<int>s;
	for (int i = 1; i <= n; i++) {
		if (state[i] != -1)s.insert(state[i]);
	}

	memset(state, -1, sizeof(state));
	state[b] = 0;
	dfs(b, b);

	for (int i = 1; i <= n; i++) {
		if (i != b && s.count(state[i])) {
			cout << "YES" << endl;
			return;
		}
	}

	cout << "NO" << endl;
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int _; _ = 1;
	cin >> _;
	while (_--)	solve();
	for (auto x : ans)cout << x << endl;
	return 0;
}
posted @ 2025-04-29 02:10  zombieee  阅读(16)  评论(0)    收藏  举报