Codeforces练习题题解(一)

A.Consecutive Points Segment-1671B

题意:

一条横轴上存在若干点,所有的点都不相同且严格递增。规定可以对每个点进行一次左移、右移或不变的操作,目标使所有点连续。

思路:

对于连续的点,当左端点或右端点移动时,所有的点需要跟随它一起移动;当两个点之间存在一个空隙,通过一次左移或右移使它们连续;当两个点之间存在两个空隙,通过左右点分别进行一次移动使它们连续。而整个轴上的空隙数量不会发生改变,也就是说空隙数量必须\leq 2,才能实现。

另解:

要使最终序列连续,则点对应位置不能相差过大,首尾点间最多有两个位置可以不填入点。因此只需要计算首尾差值就可以判断能否达成目标

代码:

解法1:

#include <bits/stdc++.h>
#define int long long
const int maxn = 2e5 + 10;

using namespace std;
int t;
int a[maxn];

signed main() {
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin >> t;
	while(t--)
	{
		int n; cin >> n;
		int cnt = 0;
		for(int i = 1; i <= n; i++){
			cin >> a[i];
		}
		for(int i = 2; i <= n; i++){
			cnt += a[i] - a[i - 1] - 1;
		}
		cout << (cnt > 2 ? "NO" : "YES") << endl;
	}
	return 0;
}

解法2:

#include <bits/stdc++.h>
#define int long long
const int maxn = 2e5 + 10;

using namespace std;
int t;
int a[maxn];

signed main() {
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin >> t;
	while(t--)
	{
		int n; cin >> n;
		int cnt = 0;
		for(int i = 1; i <= n; i++){
			cin >> a[i];
		}
		if(a[n] - a[1] <= n + 1){
			cout << "YES" << "\n";
		} else {
			cout << "NO" << "\n"; 
		}
	}
	return 0;
}

思考:

在考虑单个点的操作时,可以将其转化成多个点的共同操作,并且要遵守某个原则(在这道题当中是保持连续性),并且将每次操作所产生的影响,用一个更容易量化的东西来表达(这道题中就是中间空隙的个数)。

 B.Make it Increasing-1667A

题意:

给定长度为n的数组a,现有一个长度为n且初始化为0的数组b。

每次操作可以选择一个下标i,使bi + aibi - ai

求最少多少次操作可以使b严格升序。\left ( n\leq 5000 \right )

思路:

要使b数组严格递增,那么一定使以某个位置pos为轴,左边全做减法,右边全做加法。

n \leq 5000是可以接受O(n^{2})的复杂度的,那么可以确定一个pos位置,分别计算两边需要进行操作的次数k,贪心出最小值。

对于左边(作正数考虑):

b_{i+1}< k*a_{i}\rightarrow \frac{b_{i+1}}{a_{i}} < k

因要满足严格大于,所以左边应向下取整再+1

所以k = (b_{i +1} + a_{i}) / a_{i}

同理可得右边应为k*a_{i} > b_{i-1},得k = (b_{i-1} + a_{i})/ a_{i}

代码:

#include <bits/stdc++.h>
#define int long long
#define AC return 0;
#define endl '\n'
const int maxn = 5050;

using namespace std;
int n;
int a[maxn];
int b[maxn];

void solve() {
	int ans = 1e18;
	for(int pos = 1; pos <= n; pos++){
		int res = 0;
		for(int i = 1; i <= n; i++){
			b[i] = 0;
		}
		
		for(int i = pos - 1; i >= 1; i--){
			int k = (b[i + 1] + a[i]) / a[i];
			b[i] = k * a[i];
			res += k; 
		}
		for(int i = pos + 1; i <= n; i++){
			int k = (b[i - 1] + a[i]) / a[i];
			b[i] = k * a[i];
			res += k;
		}	
		ans = min(res, ans);
	}
	cout << ans << endl;
}

signed main() {
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin >> n;
	for(int i = 1; i <= n; i++) {
		cin >> a[i];
	}
	solve();
	AC
}

C.Permutation - 359B

题意:

给定n, k,构造一个长度为2n的排列,满足式子:

思路:

分析这个式子,前半部分是一个奇数和它后面的偶数作差的绝对值之和,后半部分是一个奇数和它后面的偶数作差之和的绝对值,那么我们先将一奇一偶记为一对。

怎么处理绝对值呢?正着看,奇数比偶数小1,作差得到一个负数,不便于处理。那么我们可以尝试构造逆序,让差值为正数,得到n - n = 0.怎么处理2k呢?如果能通过一次操作让式子结果+2,进行k次操作就可以了。现在只需要让前半部分得到1,后半部分得到-1即可,所以可以将一对数进行反转,前面通过绝对值得到1,后面通过绝对值得到-1.

代码:

#include <bits/stdc++.h>
#define int long long
#define AC return 0;
#define endl '\n'

const int N = 1e6 + 9;
int a[N];

using namespace std; 

void solve() {
	int n, k; cin >> n >> k;
	for(int i = 1; i <= 2 * n; i++) a[i] = (2 * n) - i + 1;
	for(int i = 1; i <= k; i++) swap(a[2 * i - 1], a[2 * i]);
	for(int i = 1; i <= 2 * n; i++) cout <<a[i] << ' ';
}

signed main() {
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
	int _ = 1;
	while(_--) {
		solve();
	}
	AC
}

D.Lucky Chains-1766D

题意:

当二元组(x, y)存在gcd(x, y) = 1,即x, y互质时,称为幸运的。

若有(x + 1, y + 1), (x + 2, y + 2), (x + 3, y + 3)...(x + k, y + k)都是幸运的,则称为长度为k+1的幸运链。

求给定(x, y)生成的最长幸运链的长度。

思路:

(x, y)出发,直到遇到gcd(x+t, y+t) \neq 1时停止。

gcd具有一条性质:gcd(a, b) = gcd(a, b - a)

所以终点可以化为gcd(x+t, y+t) = gcd(x+t,y-x)\neq 1.

y-x是一个确定的常数,所以问题转换成求t的最小值。

如果gcd(a,b) \neq 1,那么a, b必然存在至少一个公共的质因子,所以可以从y-x入手,对y-x分解质因数后进行枚举,设枚举数为p,那么一个可能的t取值就是最小的满足(x+t) \bmod p = 0,即:t = (-x) \bmod p

对于所有可能的t取小得到t的最小值,幸运链的长度从[0, t - 1]长度为t,则t就是答案。

要注意的问题是,每次询问进行一次O(\sqrt{x})的质因数分解显然会超时,所以需要用线性筛筛出minp,随后进行O(log)的枚举。

代码:

#include <bits/stdc++.h>
#define int long long
#define AC return 0;
#define endl '\n'

using namespace std;

const int maxn = 1e7 + 9, inf = 2e9;
int minp[maxn];

//欧拉筛
void euler(int n) {
	bitset<maxn> vis;
	vector<int> primes;
	vis[0] = vis[1] = true;
	minp[1] = 1;
	
	for(int i = 2; i <= n; ++i){
		if(!vis[i])primes.push_back(i), minp[i] = i;
		for(int j = 0; j < primes.size() && i * primes[j] <= n; ++ j){
			vis[i * primes[j]] = true;
			minp[i * primes[j]] = primes[j];
			if(i % primes[j] == 0)break;
		}
	}
}

void solve() {
	int x, y;
	cin >> x >> y;
	//特判,当y-x为1时,链长为无穷
	if(y - x == 1) cout << -1 << endl;
	else{
		int ans = inf;
		y -= x;
		
		while(y > 1){
			int p = minp[y];
			ans = min(ans, ((-x) % p + p) % p);
			while(y % p == 0) y /= p;
		}
		cout << ans << endl;
	}
}

signed main() {
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
	euler(1e7);
	int _; cin >> _;
	while(_--)solve();
	AC
}

E.Binary String-1680C

题意:

有一个01串,你可以从串头或串尾删去一些字符(可以是0个),操作后的字符串可以为空。

要求开销max(串中剩余0的数量,移除的1的数量)的最小值

思路:

1.双指针

去掉头尾的字符串,相当于在中间寻找一段满足要求的串。

设置c0记录剩余0的个数,c1记录移除1的个数,只需要维护这两个值,找到最小值作为答案。

我们不知道要截取多长的串,所以通过双指针来遍历整个串。

随着串的长度增加,c0会不变或增加,c1会不变或减少(二者不会同时发生),如果看作一个以右指针r为自变量的函数,这两个值会在某处相交而改变大小关系,而min(max(c0,c1))即使上半段函数的最小值处。

接下来要验证双指针的正确性,即左指针l右移时,右指针r必须同时右移。左指针移动会导致c0不变或减少,c1不变或增加,即c0 \leq c1一定会变成c0 < c1,会远离最佳答案,所以正确性得证。

2.前缀和

其实可以思考,需要截取的这段串的长度是否是确定的。

要让留在外面的1足够少,最理想的状态就是所有的1都是连续排列的,而这一段中每出现一个0,就意味着这一段外出现了一个1,所以确定了串的长度就是1的个数,c0和c1其实是等价的。

只需通过前缀和确定0的个数,再遍历长度为1的个数的区间,找出最小值即可。

从数学的角度来解释一下

min(max(pre[r] - pre[l-1], cnt - (r-l+1-(pre[r]-pre[l-1]))))

当取得最小值时,两者相等

pre[r] - pre[l-1]=cnt - (r-l+1-(pre[r]-pre[l-1]))

化简后有r-l+1 = cnt

代码:

#include <bits/stdc++.h>
#define int long long
#define AC return 0;
#define endl '\n'
const int maxn = 2e5+10; 

using namespace std;

char s[maxn];

void solve() {
	cin >> s + 1;
	int n = strlen(s + 1);
	int c0 = 0, c1 = 0;
	for(int i = 1; i <= n; ++i) c1 += (s[i] == '1');
	int ans = n;
    //一开始双指针指的是一个空区间
	for(int i = 1, j = 0; i <= n; ++i){
        //保持c0 < c1这个条件
		while(j + 1 <= n && c0 < c1){
			j++;
			if(s[j] == '1') c1--;
			else c0++;
		}
		ans = min(ans, max(c0, c1));
        //左指针移动的时候也要调整c0、c1
		if(s[i] == '1') c1++;
		else c0--;
	}
	cout << ans << endl;
}

signed main() {
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
	int t; cin >> t;
	while(t--){
		solve();
	}
	AC
}
#include <bits/stdc++.h>
#define int long long
#define AC return 0;
#define endl '\n'

using namespace std;

const int maxn = 2e5+10; 
int t;
int pre[maxn];

void solve() {
	string s;
	int cnt = 0;//统计1个数
	cin >> s;
	int len = s.length();
	for(int i = 1; i <= len; i++){
		//前缀和统计0的个数
		pre[i] = pre[i - 1] + (s[i-1] == '0');
		if(s[i-1] == '1') cnt++;
	}
	int res = cnt;
	for(int i = cnt; i <= len; i++){
		res = min(res, pre[i] - pre[i - cnt]);
	}
	cout << res << endl;
}

signed main() {
	cin >> t;
	while(t--){
		solve();
	}
	AC
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值