暑期集训7月4日题解


CF1661B Getting Zero

Problem - 1661B - Codeforces

考虑到2^15 = 32768,bfs也许最多跑15层,但每一层都会入队许多数,不加剪枝的情况下是2^15次,因为一个数出队都会让两个新数入队。去重剪枝后,实测极端情况下,一个数进行bfs仍需要入队最多1e4次,如此时间复杂度就过高。

考虑我们最终得到的数是2^15,与其先执行操作乘2一步步加到32768,显然先加再乘是更优的

证明:如果一个数可以通过先乘2再加一的方式得到32768,我们可以先设n经过k次乘2操作得到了数x,那么x一定是偶数,因此需要偶数次加一操作最后得到32768。我们可以假设进行了(32768-x)次加一操作,那么在乘2之前,我们只需要进行 (32768 - x) / 2^k次加一操作就能得到相同的结果,这样明显是比先乘再加更优。由于32768 = 2^15, x 也是2^k的倍数,因此(32768 - x) / 2^k一定是整数,为2^(15-k) - n

但15的范围本身并不大,不需要推公式得来,公式只用于证明正确性。我们可以枚举加一操作进行 i ∈ [0, 15]次,然后经过k次乘2操作得到0,最终答案即为min(i + k)

#include <bits/stdc++.h>
#define int long long
#define mp(A, B) make_pair(A, B)
#define sz(a) signed(a.size())
#define pb push_back
#define all(x) x.begin(), x.end()
#define endl '\n'
#define fi first
#define se second
#define eps 1e-8
#define debug(a) cout << #a << "=" << a << endl;
using namespace std;
typedef double db;
typedef pair<int, int> PII;
typedef unsigned long long ULL;
const int maxn = 400010;
const int inf = 0x3f3f3f3f;
const int mod1 = 100000007;
const int mod2 = 998244353;
const int inv_p_6 = 166374059;
const int inv_p_2 = 499122177;
int gcd(int a, int b) {return b ? gcd(b, a % b) : a;}
int lcm(int a, int b) {return a / gcd(a, b) * b;}
//int h[maxn], ne[maxn], e[maxn], w[maxn], idx;
int tt;
int n, m, k;
//int q[maxn];

void solve() {
	scanf("%lld", &n);
	int ans = 1e18;
	for (int i = 0; i <= 15; i ++ ) {
		int now = n + i, cnt = i;
		int more = 0;
		while (more < 15 && now % 32768 != 0) {
			now = (now * 2) % 32768;
			more ++ , cnt ++ ;
			if (now == 0) break;
		}
		ans = min(ans, cnt);
	}
	printf("%lld ", ans);
}

signed main() {
//	cin.tie(nullptr), cout.tie(nullptr);
//	ios::sync_with_stdio(false);
	tt = 1;
	scanf("%lld", &tt);
	while (tt -- )
	solve();
	return 0;
}

CF1644C Increase Subarray Sums

Problem - 1644C - Codeforces

数据范围只有5000,n^2枚举长度为len的最大连续子段和,然后对于k∈[0, n]分别枚举len,求出最大值。

注:初始化ans为0,代表一个都不取的情况。

#include <bits/stdc++.h>
#define int long long
#define mp(A, B) make_pair(A, B)
#define sz(a) signed(a.size())
#define pb push_back
#define all(x) x.begin(), x.end()
#define endl '\n'
#define fi first
#define se second
#define eps 1e-8
#define debug(a) cout << #a << "=" << a << endl;
using namespace std;
typedef double db;
typedef pair<int, int> PII;
typedef unsigned long long ULL;
const int maxn = 5010;
const int inf = 0x3f3f3f3f;
const int mod1 = 100000007;
const int mod2 = 998244353;
const int inv_p_6 = 166374059;
const int inv_p_2 = 499122177;
int gcd(int a, int b) {return b ? gcd(b, a % b) : a;}
int lcm(int a, int b) {return a / gcd(a, b) * b;}
//int h[maxn], ne[maxn], e[maxn], w[maxn], idx;
int tt;
int n, m, k, x;
int q[maxn];
int dp[maxn];

void solve() {
	scanf("%lld%lld", &n, &x);
	for (int i = 1; i <= n; i ++ ) {
		scanf("%lld", &q[i]);
		q[i] += q[i - 1];
	}
	
	memset(dp, -inf, sizeof dp);
	for (int len = 1; len <= n; len ++ ) {
		for (int l = 1; l + len - 1 <= n; l ++ ) {
			int r = l + len - 1;
			dp[len] = max(dp[len], q[r] - q[l - 1]);
		}
	}
	
	for (int i = 0; i <= n; i ++ ) {
		int ans = 0;
		for (int len = 1; len <= n; len ++ ) {
			if (i < len) ans = max(ans, dp[len] + i * x);
			else ans = max(ans, dp[len] + len * x);
		}
		printf("%lld ", ans);
	}
	
	puts("");
}

signed main() {
//	cin.tie(nullptr), cout.tie(nullptr);
//	ios::sync_with_stdio(false);
	int tt = 1;
	scanf("%lld", &tt);
	while (tt -- )
	solve();
	return 0;
}

CF1648B Integral Array

Problem - 1648B - Codeforces

这题很好,展开说一下:

1.刚开始我一直在想,给出范围c到底有什么用,甚至一度以为这是一个无用信息。既然给出范围c,目的就是为了限制循环,不让我们越界。而如果按照题上的要求,通过除法来考虑该题,c就是用不上的条件,因为除法不可能越除越多。因此我们需要转换一个角度,把除法转化为乘法,这样题目上给的约束c就是一个有利的条件。

2.数组内可能存在许多相等的数,这会带来许多重复计算。

根据题上给的条件,假设 :
$$
\left\lfloor\dfrac{x}{y}\right\rfloor = r
$$
由于要化除为乘,我们可以设y,r已知。那么根据下取整的性质,x是有多种情况的,具体范围是 [y * r , y * (r + 1) - 1],如果x/y=r不存在在数组中,那么对于一个不存在在数组中的r,如果数组中有在y * r的范围内存在的x,即是无解的情况。因此,我们只需要枚举y和r(y在c的限制范围内的倍数 且 不在数组中),判断数组内是否含有 [y * r , y * (r + 1) - 1]

注:

1.枚举y时,不能用1 ~ n枚举,因为数组中有许多重复元素,这样就会导致我们重复计算很多次y * c。而如果只枚举1 ~ c,数组中相同的元素只会重复一次,其实相当于起到了去重的效果

2.用前缀和进行O(1)查询。

#include <bits/stdc++.h>
//#define int long long
#define mp(A, B) make_pair(A, B)
#define sz(a) signed(a.size())
#define pb push_back
#define all(x) x.begin(), x.end()
#define endl '\n'
#define fi first
#define se second
#define eps 1e-8
#define debug(a) cout << #a << "=" << a << endl;
using namespace std;
typedef double db;
typedef pair<int, int> PII;
typedef unsigned long long ULL;
const int maxn = 1000010;
const int inf = 0x3f3f3f3f;
const int mod1 = 100000007;
const int mod2 = 998244353;
const int inv_p_6 = 166374059;
const int inv_p_2 = 499122177;
int gcd(int a, int b) {return b ? gcd(b, a % b) : a;}
int lcm(int a, int b) {return a / gcd(a, b) * b;}
//int h[maxn], ne[maxn], e[maxn], w[maxn], idx;
int tt;
int n, m, k, c;
int q[maxn];
int cnt[maxn], precnt[maxn];

void solve() {
	scanf("%d%d", &n, &c);
	for (int i = 0; i <= c; i ++ ) {
		cnt[i] = precnt[i] = 0;
	}
	for (int i = 1;i <= n; i ++ ) {
		scanf("%d", &q[i]);
		cnt[q[i]] ++ ;
	}
	
	for (int i = 0; i <= c; i ++ ) precnt[i] = precnt[i - 1] + cnt[i];
	
	for (int i = 2; i <= c; i ++ ) {
		if (cnt[i]) {
			for (int j = 1; j * i <= c; j ++ ) {
				if (!cnt[j]) {
					int r = min(c, (j + 1) * i - 1);
					if (precnt[r] - precnt[j * i - 1]) {
						puts("NO");
						return;
					}
				}
			}	
		}
	}
	puts("YES");
}

signed main() {
//	cin.tie(nullptr), cout.tie(nullptr);
//	ios::sync_with_stdio(false);
	int tt = 1;
	scanf("%lld", &tt);
	while (tt -- )
	solve();
	return 0;
}

CF1661C Water the Trees

Problem - 1661C - Codeforces

官方题解中有很妙的二分思路。

首先证明,对于操作后的结果,所有树的大小等于mx或mx+1,且不可能比mx+1更大。

假设最后树的大小均等于mx+2,那么我们可以删除一些动作,使得高度为mx, 对于mx+1来说,仅有某些情况比mx更优(如样例这种,1加到较大值而2加到较小值,且较小值和较大值只差1,这样不会浪费每一天),所以,我们要计算最终答案为mx和mx+1,从中取较小者。

如何二分?

二分天数mid,如此有操作一 (mid+1)/2 次,操作二 (mid) / 2次。首先贪心地使用操作二,有二则用二,中间的1用操作1弥补,如果操作二用完,可以用两次操作一替代操作二。

#include <bits/stdc++.h>
#define int long long
#define mp(A, B) make_pair(A, B)
#define sz(a) signed(a.size())
#define pb push_back
#define all(x) x.begin(), x.end()
#define endl '\n'
#define fi first
#define se second
#define eps 1e-8
#define debug(a) cout << #a << "=" << a << endl;
using namespace std;
typedef double db;
typedef pair<int, int> PII;
typedef unsigned long long ULL;
const int maxn = 1000010;
const int inf = 0x3f3f3f3f;
const int mod1 = 100000007;
const int mod2 = 998244353;
const int inv_p_6 = 166374059;
const int inv_p_2 = 499122177;
int gcd(int a, int b) {return b ? gcd(b, a % b) : a;}
int lcm(int a, int b) {return a / gcd(a, b) * b;}
//int h[maxn], ne[maxn], e[maxn], w[maxn], idx;
int tt;
int n, m, k, mx;
int q[maxn];

bool check (int x) {
	int op2 = x / 2, op1 = x - op2;
	for (int i = 1; i <= n; i ++ ) {
		int res = (mx - q[i]) / 2;
		if ((mx - q[i]) & 1) op1 -- ;
		if (op2 >= res) op2 -= res;
		else {
			res -= op2;
			op2 = 0;
			op1 -= res * 2;
		}
	}
	return op1 >= 0;
}

void solve() {
	scanf("%lld", &n);
	mx = 0;
	for (int i = 1; i <= n; i ++ ) {
		scanf("%lld", &q[i]);
		mx = max(mx, q[i]);
	}
	
	int l = 0, r = 1e18;
	while (l < r) {
		int mid = l + r >> 1;
		if (check(mid)) r = mid;
		else l = mid + 1;
	}
	int ans1 = l;
	
	mx ++ ;
	l = 0, r = 1e18;
	while (l < r) {
		int mid = l + r >> 1;
		if (check(mid)) r = mid;
		else l = mid + 1;
	}
	int ans2 = l;
	
	printf("%lld\n", min(ans1, ans2));
}

signed main() {
//	cin.tie(nullptr), cout.tie(nullptr);
//	ios::sync_with_stdio(false);
	int tt = 1;
	scanf("%lld", &tt);
	while (tt -- )
	solve();
	return 0;
}

CF1667A Make it Increasing

还是比较简单的一道题,找到某个分界点为0,让左边的值都小于0,右边的值都大于0。

数据范围很小,O(n^2)暴力枚举所有分界点。

#include <bits/stdc++.h>
#define int long long
#define mp(A, B) make_pair(A, B)
#define sz(a) signed(a.size())
#define pb push_back
#define all(x) x.begin(), x.end()
#define endl '\n'
#define fi first
#define se second
#define eps 1e-8
#define debug(a) cout << #a << "=" << a << endl;
using namespace std;
typedef double db;
typedef pair<int, int> PII;
typedef unsigned long long ULL;
const int maxn = 200010;
const int inf = 0x3f3f3f3f;
const int mod1 = 100000007;
const int mod2 = 998244353;
const int inv_p_6 = 166374059;
const int inv_p_2 = 499122177;
int gcd(int a, int b) {return b ? gcd(b, a % b) : a;}
int lcm(int a, int b) {return a / gcd(a, b) * b;}
//int h[maxn], ne[maxn], e[maxn], w[maxn], idx;
int tt;
int n, m, k;
int q[maxn];

void solve() {
	scanf("%lld", &n);
	for (int i = 1; i <= n; i ++ ) scanf("%lld", &q[i]);
	
	int ans = 1e18;
	for (int mid = 1; mid <= n; mid ++ ) {
		int now = 0;
		int ls = 0;
		for (int i = mid + 1; i <= n; i ++ ) {
			int res = ls / q[i];
			res ++ ;
			ls = res * q[i];
			now += res;
		}
//		printf("%lld\n", ans);
		ls = 0;
		for (int i = mid - 1; i >= 1; i -- ) {
			int res = ls / q[i];
			res -- ;
			ls = res * q[i];
			now += abs(res);
		}
//		printf("%lld\n", ans);
		ans = min(ans, now);
	}
	printf("%lld\n", ans);
}

signed main() {
//	cin.tie(nullptr), cout.tie(nullptr);
//	ios::sync_with_stdio(false);
	int tt = 1;
//	scanf("%lld", &tt);
	while (tt -- )
	solve();
	return 0;
}

CF1660F Promising String

Problem - 1660F1 - Codeforces

Problem - 1660F2 - Codeforces

先说ezver,数据范围5000所以可以n^2,因此可以找出所有lr。

判断能否使用操作的方法:根据抽屉原理,如果’-‘的个数大于’+’个数2个以上,那么一定会有两个’-‘并排。假设当前’+’和’-‘的个数是a和b,如果执行一次操作,b的个数减少2,a的个数增加2,即ab差值减3,而要ab相等,即差值为0,所以枚举前缀和,如果差值%3为0,即为合法答案。

#include <bits/stdc++.h>
#define int long long
#define mp(A, B) make_pair(A, B)
#define sz(a) signed(a.size())
#define pb push_back
#define all(x) x.begin(), x.end()
#define endl '\n'
#define fi first
#define se second
#define eps 1e-8
#define debug(a) cout << #a << "=" << a << endl;
using namespace std;
typedef double db;
typedef pair<int, int> PII;
typedef unsigned long long ULL;
const int maxn = 200010;
const int inf = 0x3f3f3f3f;
const int mod1 = 100000007;
const int mod2 = 998244353;
const int inv_p_6 = 166374059;
const int inv_p_2 = 499122177;
int gcd(int a, int b) {return b ? gcd(b, a % b) : a;}
int lcm(int a, int b) {return a / gcd(a, b) * b;}
//int h[maxn], ne[maxn], e[maxn], w[maxn], idx;
int tt;
int n, m, k;
int q[maxn];

void solve() {
	scanf("%lld", &n);
	string a;
	a.resize(n);
	scanf("%s", a.c_str());
	
	vector<int> plus(n + 1), minus(n + 1);
	for (int i = 0; i < a.size(); i ++ ) {
		plus[i] = plus[i - 1], minus[i] = minus[i - 1];
		if (a[i] == '+') plus[i] ++ ;
		else minus[i] ++ ;
	}
	
	int ans = 0;
	for (int i = 0; i < n; i ++ ) {
		for (int j = i + 1; j < n; j ++ ) {
			int pls = plus[j] - plus[i - 1], mns = minus[j] - minus[i - 1];
			if (pls == mns) ans ++ ;
			else if (mns - pls >= 2) {
				while (mns > pls) {
					mns -= 3;
					if (mns == pls) {
						ans ++ ;
						break;
					}
				}
			}
		}
	}
	
	printf("%lld\n", ans);
}

signed main() {
//	cin.tie(nullptr), cout.tie(nullptr);
//	ios::sync_with_stdio(false);
	int tt = 1;
	scanf("%lld", &tt);
	while (tt -- )
	solve();
	return 0;
}

hardver:

数据范围大后不能再枚举所有lr了,但是可以只枚举右边界r,来判断合法的左边界。

如何判断?

设’+’个数为a,’-‘个数为b

首先考虑合法的条件,根据ezver的推论可知,需要ab的差值%3为0,且b>=a,(这是因为C语言中的取余无法判断正负。

约束一:对于某个q[i]的贡献,我们只看之前和q[i]%3相等的那些数,与其他的数无关,所以我们可以单独把%3相等的放在同一组中判断。

约束二:如果要查询之前出现的所有b>=a的情况。首先考虑使用前缀和数组q,将+和-的权值变为-1和+1,那么如果b>=a,就是q[r] >= q[l],我们可以边遍历边计算,对于当前遍历到的r,在它的同余组中,只有小于它的q[l]才会被计算贡献,因此维护三个树状数组(三个约数,进行查逆序对的操作。

#include <bits/stdc++.h>
#define int long long
#define mp(A, B) make_pair(A, B)
#define sz(a) signed(a.size())
#define pb push_back
#define all(x) x.begin(), x.end()
#define endl '\n'
#define fi first
#define se second
#define eps 1e-8
#define debug(a) cout << #a << "=" << a << endl;
using namespace std;
typedef double db;
typedef pair<int, int> PII;
typedef unsigned long long ULL;
const int maxn = 200010;
const int inf = 0x3f3f3f3f;
const int mod1 = 100000007;
const int mod2 = 998244353;
const int inv_p_6 = 166374059;
const int inv_p_2 = 499122177;
int gcd(int a, int b) {return b ? gcd(b, a % b) : a;}
int lcm(int a, int b) {return a / gcd(a, b) * b;}
//int h[maxn], ne[maxn], e[maxn], w[maxn], idx;
int tt;
int n, m, k;
int q[maxn];
int tr[4][maxn << 1 | 1];

int lowbit (int x) {
	return x & -x;
}

void add (int c, int x, int k) {
	for (; x <= (n << 1 | 1); x += lowbit(x)) tr[c][x] += k;
}

int query (int c, int x) {
	int ans = 0;
	for (; x; x -= lowbit(x)) ans += tr[c][x];
	return ans;
}

void solve() {
	scanf("%lld", &n);
	string a; a.resize(n);
	scanf("%s", a.c_str());
	
	for (int i = 0; i < 3; i ++ ) {
		for (int j = 0; j <= (n << 1 | 1); j ++ ) tr[i][j] = 0;
	}
	
	int now = n + 1;
	for (int i = 1; i <= n; i ++ ) {
		q[i] = q[i - 1];
		if (a[i - 1] == '-') q[i] ++ ;
		else q[i] -- ;
	}
	puts("");
	
	int ans = 0;
	for (int i = 0; i <= n; i ++ ) {
		ans += query((q[i] % 3 + 3) % 3, q[i] + now);
		add((q[i] % 3 + 3) % 3, q[i] + now, 1);
	}
	
	printf("%lld\n", ans);
}

signed main() {
//	cin.tie(nullptr), cout.tie(nullptr);
//	ios::sync_with_stdio(false);
	int tt = 1;
	scanf("%lld", &tt);
	while (tt -- )
	solve();
	return 0;
}

第一天集训结束,congratulations!


文章作者: Cedeat
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Cedeat !
  目录