Codeforces Round #780 (Div. 3) A~F2 题解

Codeforces Round #780 (Div. 3) A~F2题解

A.Vasya and Coins

题意 你有a枚一元硬币和b枚2元硬币,问你最小不能组合的零钱数

思路 显然,没有一元硬币的话是组合不出一元的,其他情况下最小不能组合的零钱数就为2*b+a

AC代码

#include
using namespace std;
const int N = 1e6+7;
typedef long long ll;
ll n,A[N],m; 
int main(){
	int T;
	cin>>T;
	while(T--){
		cin>>n>>m;
		if(n==0){
			cout<<"1\n";
		}
		else {
			cout<<n+2*m+1<<endl;
		}
	}
	return 0;
}

B. Vlad and Candies

题意

你收到一包含有n种类的糖果,你每次不能连续两次吃相同种类的糖果并且只能吃数量最多的糖果,问你能不能把所有糖吃完

思路

考虑不能吃完的情况,那必定是有最大数量的糖果 - 较小数量的糖果 > 1,那样必须连续吃两次最大数量的糖果。
那么问题转化为,是否存在存在某个糖果能够减少到跟当前糖果数量一样 ,显然,如果说最大数量的两个糖果满足相差小于1,那么他们就可以减少到任意数量,答案就为YES,否则为NO

AC代码

#include
using namespace std;
const int N = 1e6+7;
typedef long long ll;
ll n,A[N],m; 
int main(){
	int T;
	cin>>T;
	while(T--){
		cin>>n;
		ll sum = 0;
		for(int i = 0;i<n;i++){
			cin>>A[i];
			sum += A[i];
		}
		sort(A,A+n);
		int ok= 1;
		if(A[n-1]-A[n-2]>1) ok = 0;
		if(ok){
			cout<<"YES\n";
		}
		else cout<<"NO\n";
	}
	return 0;
}

C. Get an Even String

题意

定义一个字符串是合法的满足 1. 长度为偶数 2. 所有奇数位的字符都等于下一个字符
例如“aabb”,“ttllqq”是合法的,给你你个字符串,问你最少删除多少个字串能令其变成合法字符串

思路

贪心来想 , 先遇到哪两个相同的字符就保留这两个字符,删除这两个字符其余部分,例如s = ababaac,先遇到的是aa,那么aa中间的b就舍弃,再到s[3] = b,找下一个b 的过程中发现已经有两个a匹配了,那就删去上一个匹配位置到这次匹配位置之间的字符,即b,最后别忘了处理一下末尾字符是否匹配了

具体实现

定义一个数组A标记字符是否出现过,变量 l 记录上次匹配成功的下标,如果 在遍历时 字符已经出现一次,那么立即匹配,删除 l 到当前下标的字符,再重新让A数组的元素设为0,更新 l 的位置,最后如果l的位置不是最后一位,那么也要删去l到最后一位

AC代码

#include
using namespace std;
const int N = 1e6+7;
typedef long long ll;
ll n,m;
bool A[200];
int main(){
	int T;
	cin>>T;
	while(T--){
		string s;
		cin>>s;
		memset(A,0,sizeof(A));
		int n = s.length(); 
		int l = 0;
		int ans = 0;
		//谁先出现就匹配谁 
		for(int i = 0;i<n;i++){
			if(A[s[i]]) {
				ans += i-l-1;
				l = i+1;
				for(int i = 'a';i<='z';i++) A[i] = 0;
			}
			else {
				A[s[i]] = 1;
			}
			//M[s[i]] = i;
		}
		if(l!=n) ans += n-l;
		cout<<ans<<endl;
	}
	return 0;
}
//acb

D. Maximum Product Strikes Back

题意

给你一个元素大小绝对值小于等于2的数组,你可以从头和从尾部删去若干个数,问你怎么删去原色能使剩下元素中乘积最大,如果数组为空,乘积为 1

思路

  1. 剩下的数组中不能含有0,因为乘积的最小值为1,所以查找区间是以0为分割线
  2. 当查找区间负数个数为偶数时,区间不用修改
  3. 当查找区间负数个数为奇数时,需要剔除从左到右最近的负数或者从右到左最近的负数
  4. 因为影响乘积大小的只有2和-2,所以记录2和-2的数量代表乘积,否则直接乘爆longlong

AC代码

#include
using namespace std;
const int N = 1e6+7;
typedef long long ll;
vector<int> rc;
vector<int> sign;
vector<int> val;
int A[N];
int main(){
	int T;
	cin>>T;
	// 首先切割0位置,预处理出区域内负数的个数 
	while(T--){
		int s = 0; // 2 的数量 ,直接乘放不下
		int si = 1; // 符号 
		int n;
		cin>>n;
		rc.clear();
		sign.clear();
		val.clear();
		memset(A,0,sizeof(A));
		int sum = 0;
		for(int i = 1;i<=n;i++){
			cin>>A[i];
			if(abs(A[i])>1) sum = 1;
			if(A[i]==0){
				rc.push_back(i),sign.push_back(si);
				val.push_back(s);
				s = 0,si = 1;
			}
			else {
				if(A[i]==2||A[i]==-2) s++;
				if(A[i]<0) si *= -1; 	
			}
		}
		if(sum==0) {
			cout<<n<<" "<<0<<endl;
			continue;
		}
		sign.push_back(si);
		rc.push_back(n+1);
		val.push_back(s);
		int l = 1;
		ll ans_l = n+1,ans_r = n ,maxx = 0; 
		for(int i = 0;i<rc.size();i++){
			int r = rc[i]-1;
			if(l<=r){
			int s = val[i];
			int si = sign[i];
			if(si>0&&s>=maxx) ans_l = l,ans_r = r,maxx = s;
			else if(si<0){
				ll mul = s,mul2 = s;
				int tl,tr;
				for(tl = l;tl<=r&&A[tl-1]>=0;tl++){
					if(A[tl]==2||A[tl]==-2) mul--;
				}
				for(tr = r;tr>=l&&A[tr+1]>=0;tr--){
					if(A[tr]==2||A[tr]==-2) mul2--;
				}
			
				if(mul>=maxx) ans_l = tl,ans_r = r,maxx= mul;
				if(mul2>=maxx) ans_l = l,ans_r = tr,maxx= mul2;
			}  
			} 
			l = rc[i]+1;
		}
		//答案
		cout<<ans_l-1<<" "<<n-ans_r<<endl; 
	}
	return 0;
}
// -2 -1 0 1 2 
//  2 -1 1 2 2 

E. Matrix and Shifts

题意

给一个二维数组,可以上下左右滑动,类似成环,有一种操作可以让某一个位置取反,问最少需要多少次取反能够使矩阵变成单位矩阵(即除对角线外都是0)

思路

  1. 上下左右滑动 可以通过复制数组 G[i][j] =G[i+n][j]=G[i][j+n]=G[i+n][j+n] ,枚举矩阵的左上角,固定宽度和长度就可以首先模拟矩阵上下左右滑动的效果
  2. 对于每个矩阵来说,变为单位矩阵的代价就是所有1的个数减对角线1的个数加对角线0的个数,其中,所有1的个数可以预处理出来
  3. 但是如果暴力枚举所有矩阵数对角线,复杂度是O(n3),显然不行,
  4. 我们考虑优化,其实问题就是数出所有在长度为n的对角线1的个数和0的个数,那么我们只需要考虑每条对角线的起点,维护一个对角线长度为n中1的个数和0的个数,一直更新答案即可,复杂度为On2

AC代码

#include
using namespace std;
const int N = 1e6+7;
typedef long long ll;
int G[5100][5010];
int col[2010],row[2010];
int main(){
	int T;
	cin>>T;
	while(T--){
		int n;
		cin>>n;
		int cnt = 0;
		for(int i = 1;i<=n;i++){
			for(int j = 1;j<=n;j++){
				char ch;
				cin>>ch;
				if(ch=='1')cnt++,G[i][j] =G[i+n][j]=G[i][j+n]=G[i+n][j+n] = 1;
				else G[i][j] =G[i+n][j]=G[i][j+n]=G[i+n][j+n] =  0;
				//形成环 
			}
		}
		int ans = INT_MAX; 
		for(int i = 1;i<=n;i++){
			int cnt1 = 0;
			int cnt0 = 0; 
			for(int k = 0;i+k<=2*n&&1+k<=2*n;k++){ //遍历斜对角线 
				if(G[i+k][1+k]==1) cnt1++;
				else cnt0++;
				if(k>=n-1){	
					ans = min(cnt-cnt1+cnt0,ans);
					if(G[i+k-n+1][1+k-n+1]==1) cnt1--;
					else cnt0++;
				}
			} 
		}
		for(int j = 1;j<=n;j++){
			int cnt1 = 0;
			int cnt0 = 0; 
			for(int k = 0;1+k<=2*n&&j+k<=2*n;k++){ //遍历斜对角线 
				if(G[1+k][j+k]==1) cnt1++;
				else cnt0++;
				if(k>=n-1){	
					ans = min(cnt-cnt1+cnt0,ans);
					if(G[1+k-n+1][j+k-n+1]==1) cnt1--;
					else cnt0++;
				}} 
		}
		cout<<ans<<endl;
	}
	return 0;
}

F1. Promising String (easy version)

题意 :

F1和F2题意一样,区别为F1的n为1≤n≤3000,F2为1≤n≤2e5,给你只含“+”,“-”的字符串,你可以把相邻两个“-”转化为“+” , 问有多少个子字符串可以通过这种操作使“+”数量等于“-”,例如“±”,“±–”,都是合法的

思路

前缀和sum处理出每个下标”-“与”+“的差
暴力枚举子字符串,当sum[j] >= sum[i]同时(sum[j]-sum[i])%3 == 0, 这个字符串就可以是合法的

AC代码

#include
using namespace std;
const int N = 1e6+7;
typedef long long ll;
int sum[N]; 
char s[N];
int main(){
	int T;
	cin>>T;
	while(T--){
		int n;
		memset(sum,0,sizeof(sum));
		cin>>n>>(s+1);
		for(int i = 1;i<=n;i++){
			sum[i] = sum[i-1]+(s[i]=='+'); 
		}
		int ans = 0;
		for(int i = 1;i<=n;i++){
			for(int j = i+1;j<=n;j++){
				int puls = sum[j]-sum[i-1];
				int minus = j-i+1 - puls;
				if(minus>=puls&&(minus-puls)%3==0){
				//	cout<
					ans++;
				}
				}
			}
		
		cout<<ans<<endl;
	}
	return 0;
}

F2 - Promising String (hard version)(前缀和树状数组)

题意

与F1一样,只不过n是1e5

思路

显然不能暴力枚举子字符串 , 我们来看关键的判断条件s[ j ] >= s[ i ] 和 (sum[j]-sum[i])%3 == 0, 其中后者可以转化成 sum[i] ==sum[j] (在模3的意义下)
那么做法就是: 枚举每个子字符串的终点,统计出前面sum[j] >= sum[ i ] 同时 sum[j]%3 ==sum[i] %3的数量就好,那么对于每个点 i 我们可以用三个树状数组分别标记一下sum[i] ,要查询贡献度时在相应的树状数组中用前缀和相加就可以得到符合要求起点的数量。

例如{1,2,3,4} 对于sum[0] = 1, 模3也为1 ,那么在第二个树状数组中(第一个是记录模数为0的)记录一下sum[0]已经出现过,在遍历到sum[3] = 4,模数也为1,查询第二个树状数组中1~sum[4]的前缀和,就是这个点的贡献值;

但是由于sum数组会有负数,所以最后加上一个偏移量,让所有数大于0,不影响sum[j]-sum[i])%3 == 0的判断

AC代码

#include
using namespace std;
const int N = 1e6+10;
typedef long long ll;
int n; 
int tree[N][3];
void add(int ix,int x,int m){
	for(int i = ix;i<=N;i+= (i&-i)){
		tree[i][m] += x;
	}
}
ll query(int ix,int m){
	ll ans = 0;
	for(int i = ix;i>=1;i-=(i&-i)){
		ans += tree[i][m];
	}
	return ans;
}
char s[N];
int sum[N];
int main(){
	ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
	int T;
	cin>>T;
	while(T--){
		cin>>n;
		//string s;
		cin>>(s+1);
		int minn = 0;
		sum[0] = 0;
		for(int i = 1;i<=n;i++){
			sum[i] = sum[i-1]+(s[i]=='+'?-1:1);			
			minn = min(minn,sum[i]);
		}
		for(int i = 0;i<=n-minn+10;i++) tree[i][0]=tree[i][1]=tree[i][2] = 0;
		ll res = 0;
		for(int i = 0;i<=n;i++) sum[i] -= minn-1;
		for(int i = 0;i<=n;i++){
			int need = sum[i]%3;
			res += query(sum[i],need);
			//cout<
			add(sum[i],1,need);
		} 
		cout<<res<<'\n';
	}
	return 0;
}

你可能感兴趣的:(codeforce,数据结构,算法,贪心算法,字符串,c语言)