Codeforces Round #830 (Div. 2)(A~D)

A. Bestie

给出一个数组a,通过下面若干次操作使得所有数的gcd为1:选择任意一个数,将它换成gcd(a[i],i),代价是n-i+1,计算将数组变为满足条件所需的最小代价。

思路:首先我们需要知道,只要数组中有两个数互质就可以满足条件,因为要最小化代价,所以从最后向前修改一定是最优的,如果数组已经满足条件了,那代价为0;若修改最后一个可以满足条件,那就只修改最后一个,代价为1;若只修改倒数第二个就可以满足条件,那代价为2;若前面的操作都不能满足条件,那一定是要修改两个,即倒数两个,代价为3。

AC Code:

#include <bits/stdc++.h>

typedef long long ll;
const int N=1e5+5;
int t,n;
int a[N];

int main(){
	std::ios::sync_with_stdio(false);
	std::cin.tie(0);
	std::cout.tie(0);
	std::cin>>t;
	while(t--){
		std::cin>>n;
		for(int i=1;i<=n;i++){
			std::cin>>a[i];
		}
		int gcd=std::__gcd(a[1],a[2]);
		for(int i=3;i<=n;i++){
			gcd=std::__gcd(gcd,a[i]);
		}
		if(n==1){
			if(a[1]==1) std::cout<<0<<'\n';
			else std::cout<<1<<'\n';
			continue;
		}
		if(gcd==1){
			std::cout<<0<<'\n';
			continue;
		}
		int x=a[n];
		a[n]=std::__gcd(a[n],n);
		gcd=std::__gcd(a[1],a[2]);
		for(int i=3;i<=n;i++){
			gcd=std::__gcd(gcd,a[i]);
		}
		if(gcd==1){
			std::cout<<1<<'\n';
			continue;
		}
		a[n]=x;
		a[n-1]=std::__gcd(a[n-1],n-1);
		gcd=std::__gcd(a[1],a[2]);
		for(int i=3;i<=n;i++){
			gcd=std::__gcd(gcd,a[i]);
		}
		if(gcd==1){
			std::cout<<2<<'\n';
			continue;
		}
		std::cout<<3<<'\n';
	}
	return 0;
}

 B. Ugu

给出一个01串,每次可以选择一个位置,将这个位置以后的所有数全部翻转,即0变为1,1变为0,求最少的操作次数,使得01串变为非递减序列。

思路:贪心求解,每次遇到与前一个状态不同的都要翻转,要保证0在前。

AC Code:

#include <bits/stdc++.h>

typedef long long ll;
const int N=1e5+5;
int t,n;
std::string s;

int main(){
	std::ios::sync_with_stdio(false);
	std::cin.tie(0);
	std::cout.tie(0);
	std::cin>>t;
	while(t--){
		std::cin>>n;
		std::cin>>s;
		int cnt=0;
		int flag=0;
		for(int i=1;i<n;i++){
			if(s[i]<s[i-1]&&!flag){
				cnt++;
				flag^=1;
			} 
			if(s[i]>s[i-1]&&flag){
				cnt++;
				flag^=1;
			}
		}
		std::cout<<cnt<<'\n';
	}
	return 0;
}

C1. Sheikh (Easy version) 

给出一个数组a,只有一个询问,给出L,R,在a[L],a[R]之间找一个区间[l,r],使得区间内所有数之和-区间内所有数异或和的值最大且长度最短。

思路: 分析可知,定义的f(l,r)函数必定是随着区间长度变长而增加,因为数字中无负值,异或和每加入一个数异或,只能小于等于之前的值,所以这个函数是单调递增的,我们可以采用二分解决。考虑对于长度二分,check函数内枚举左端点,根据最长的区间的函数值判断修改左右端点。

AC Code:

#include <bits/stdc++.h>

typedef long long ll;
const int N = 2e5 + 5;
int t, pos1, pos2;
ll n, q, ans, L, R;
ll dif[N], xo[N], a[N];

bool check(int mid) {
	for (int i = 1; i <= n - mid + 1; i ++) {
		ll res = dif[i + mid - 1] - dif[i - 1] - (xo[i + mid - 1] ^ xo[i - 1]);
		if (res == ans) {
			pos1 = i, pos2 = i + mid - 1;
			return true;
		}
	}
	return false;
}

int main () {
	std::ios::sync_with_stdio(false);
	std::cin.tie(0);
	std::cout.tie(0);
	std::cin >> t;
	while (t --) {
		std::cin >> n >> q;
		for (int i = 1; i <= n; i ++) {
			std::cin >> a[i];
			dif[i] = dif[i - 1] + a[i];
			xo[i] = xo[i - 1] ^ a[i];
		}
		pos1 = 0, pos2 = 0;
		while (q --) {
			std::cin >> L >> R;
			ans = dif[R] - dif[L - 1] - (xo[R] ^ xo[L - 1]);
			int l = 1, r = n;
			while (l < r) {
				int mid = l + r >> 1;
				if (check(mid)) 
					r = mid;
				else
					l = mid + 1;
			}
			if (l == n) pos1 = 1, pos2 = n;
			std:: cout << pos1 << ' ' << pos2 << '\n';
		}
	}
	return 0;
}

C2. Sheikh (Hard Version)

题意同C1,但是q的范围与n相同,不再是1了。 

思路:如果还是按照C1的思路,时间复杂度为O(nqlogn),肯定不行,考虑优化。首先我们对于开头结尾的0而言,去掉不影响结果,所以一定要去掉;对于中间的数,我们可以定左端点,二分找右端点,需要处理每个数后面最近的非0数,枚举这些数为左端点;最重要的优化是,因为数据范围可以用64位(大约)二进制数表示,所以最多删去64个数,仅当每个数对于答案中某一位的1有唯一的贡献,例如若答案是111111,则最多有6个数对答案有贡献,即每个数贡献答案中的一位。为什么从左侧删除呢,是因为其实而言是整个数组最多删除64个数(大约),但是这64个数也有可能是左侧的,且因为右端点我们二分查找解决了,所以可以仅考虑左端点。所以n次的枚举变成了仅有64次,时间复杂度O(64*qlogn),可以过。

AC Code:

#include <bits/stdc++.h>

typedef long long ll;
const int N = 1e5 + 5;
const int mod = 1e9 + 7;
#define INF 0x3f3f3f3f
int t, n, q, L, R;
ll a[N], sum[N], xo[N];
int next[N];

int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(0);
	std::cout.tie(0);
	std::cin >> t;
	while(t --) {
		std::cin >> n >> q;
		for(int i = 1; i <= n; i ++) {
			std::cin >> a[i];
		}
		for(int i = 1; i <= n; i ++) {
			sum[i] = sum[i - 1] + a[i];
			xo[i] = xo[i - 1] ^ a[i];
		}
		next[n] = INF;
		for(int i = n - 1; i >= 1; i --) {
			if(a[i + 1] > 0)
				next[i] = i + 1;
			else
				next[i] = next[i + 1];
		}
		int st, cnt, len, l, r;
		while(q --) {
			std::cin >> L >> R;
			ll ans = sum[R] - sum[L - 1] - (xo[R] ^ xo[L - 1]);
			st = L, cnt = 64; len = R - L + 1, l = L, r = R;
			while(st <= R) {
				if(sum[R] - sum[st - 1] - (xo[R] ^ xo[st - 1]) < ans)
					break;
				int left = st, right = R, mid;
				while(left <= right) {
					mid = left + right >> 1;
					if(sum[mid] - sum[st - 1] - (xo[mid] ^ xo[st - 1]) == ans)
						right = mid - 1;
					else
						left = mid + 1;
				}
				if(len > left - st + 1)
					len = left - st + 1, l = st, r = left;
				if(a[st] > 0) {
					if(!cnt) break;
					cnt--;
				} 
				st = next[st];
				if(st == INF)
					break;
			}
			std::cout << l << ' ' << r << '\n';
		}
	}
	return 0;
}

D1. Balance (Easy version)

有一个一开始只有0的集合,每次有两种操作,可以向集合中加入一个数x,也可以询问集合的最小的不在集合中的x的倍数。

思路:暴力求解即可,但是对于前面出现过的数字,在下一次再开始查找时要从之前的数字开始,这样做一个简单的优化,保证不会TLE。

AC Code:

#include <bits/stdc++.h>

typedef long long ll;
const int N=1e5+5;
int q;

int main(){
	std::ios::sync_with_stdio(false);
	std::cin.tie(0);
	std::cout.tie(0);
	std::cin>>q;
	std::set<ll>ss;
	ss.insert(0);
	std::map<ll,ll>mp;
	while(q--){
		char c;
		ll x;
		std::cin>>c>>x;
		if(c=='+'){
			ss.insert(x);			
		}
		else{
			ll t=x,tmp=mp[t];
			if(tmp&&!ss.count(mp[t])){
				std::cout<<mp[t]<<'\n';
				continue;
			}
			if(tmp!=0) t=mp[t];
			while(ss.count(t)){
				t+=x;
			}
			mp[x]=t;
			std::cout<<mp[x]<<'\n';
		}
	}
	return 0;
}

os:一开始因为集合中没加0WA5了。。。xD

D2. Balance (Hard version)

题意与D1相同,只是加入一个删除操作。

思路:参考hpgg的思路!在C1的基础上再开一个set存储被删除的数,每次加入时如果这个set中有这个数,那就从原来集合中删去,在输出答案时,每次在被删除的set中二分找第一个大于x的数输出,如果找到的数大于mp[x]了,就从mp[x]开始暴力查询结果。

AC Code:

#include <bits/stdc++.h>

typedef long long ll;
const int N = 1e5 + 5;
int q;

int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(0);
	std::cout.tie(0);
	std::cin >> q;
	std::set<ll> ss, del;
	ss.insert(0);
	std::map<ll, ll> mp;
	while(q --) {
		char c;
		ll x;
		std::cin >> c >> x;
		if(c == '+') {
			if(del.count(x)) del.erase(x);
			ss.insert(x);			
		}
		else if(c == '-') {
			ss.erase(x);
			del.insert(x);
		}
		else {
			ll t = x;
			if(!mp[t]) {
				while(ss.count(t))
					t += x;
				mp[x] = t;
				std::cout << mp[x] << '\n';
			}
			else {
				auto it = del.lower_bound(x);
				bool flag = false;
				for(it; it != del.end(); it ++) {
					if(*it > mp[x]) break;
					if(*it % x == 0) {
						std::cout << *it << '\n';
						flag = true;
						break;
					}
				}
				if(!flag) {
					t = mp[x];
					while(ss.count(t))
						t += x;
					std::cout << t << '\n';
					mp[x] = t;
				}
			}
		}
	}
	return 0;
}

os:部分题解感觉是在胡言乱语,,若有错误请指教

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值