Codeforces Round #616 Div.2 补题

6 篇文章 0 订阅

A. Even But Not Even

题意:给你一个超大整数,问你能不能通过删除在其中的一些数字,来使得它的各位数位数字和为偶数,但其本身是奇数,删完之后的数字至少有一位,而且不能带前导0),多组用例,如果不能,输出-1,如果有多种答案,输出其中任意一个即可。

输入
4
4
1227
1
0
6
177013
24
222373204424185217171912
输出
1227
-1
17703
2237344218521717191
想法

首先,一位数绝对不可能满足题意(不可能同时为奇数和偶数,故遇到这样的样例直接输出-1。
如果给出的样例本身就是满足题意的数字,那么输出它即可。
如果上面两种都不是,那么我们就要删数字了,首先要让他是奇数,所以我们得不停地删除尾部的偶数直至尾部是奇数。
如果删完尾部后,数位只剩一个,则说明无法满足题意,输出-1,如果删完之后位数不为一切数位和为偶数,那么输出删完数字以后的数即可,如果是奇数,那么我们尝试在找一个最后一位之前的奇数删掉,使得和为偶数即可,如果找不到说明不能满足题意

代码
#include <bits/stdc++.h>
using namespace std;
const int N = 3050;
char s[N];
inline int getsum(char *s)
{
	int len = strlen(s);
	int sum = 0;
	for(int i = 0; s[i]; i++) sum += s[i] - '0';
	return sum;
} 
int main()
{
	int T;
	cin >> T;
	while(T--)
	{
		int n;
		
		scanf("%d %s", &n, s);
		int l = strlen(s), j = l - 1;	
		if(strlen(s) == 1)
		{
			printf("-1\n");
			continue;
		}
		else if(getsum(s) % 2 == 0 and (s[l - 1] - '0') % 2){
			printf("%s\n", s);
			continue;
		}
		while((s[j] - '0') % 2 == 0 and j > 0) s[j--] = '\0', n--;
		if(j != 0 and getsum(s) % 2 == 0) printf("%s\n", s);
		else
		{
			if(n == 1)
			{
				printf("-1\n");
				continue;
			}
			int flag = 0;
			for(int i = 0; s[i + 1]; i++)
			{
				if((s[i] - '0') % 2)
				{
					flag = 1;
					for(int j = i; s[j]; j++) s[j] = s[j + 1];
					break;
				}
			}
			int pos = 0;
			while(s[pos] == '0') pos++;
			if(flag) printf("%s\n", s + pos);
			else printf("-1\n");
		}
	}
	return 0;
} 

B. Array Sharpening

题意:给你一个数组,问你能不能通过削减其中的一些数(但不能削减到0以下),来使得数组呈现出a1 < a2 < … < ak 且 ak >ak+1 >…> an的状态(严格递增或者递减的数组也算),如果可以,输出Yes,不行则输出No
多组用例

输入
10
1
248618
3
12 10 8
6
100 11 15 9 7 8
4
0 1 1 0
2
0 0
2
0 1
2
1 0
2
1 1
3
0 1 0
3
1 0 1
输出
Yes
Yes
Yes
No
No
Yes
Yes
Yes
Yes
No
想法

主要是如何判定一个数组能被削减成“单峰”状态,
可以枚举这个山峰的位置然后判定,然后喜提TLE
我们容易想到,最简单的满足的这样性质的数组是0,1,2,…,k, …2,1,0,而满足题目要求的数组,都可以最终变成这样
观察它,可以发现在k之前都有 ai >= i - 1(到左端点的距离),所以 ai < i -1处即是下坡的开始,可以检查从第一个ai < i 位置往后的所有ai是否满足ai >= n - i(到右端点的距离)
也可以就在原数组的基础上修改,我们假定后半部分能够通过削减满足严格递减的性质,每次削减我们都选择把该位削减成min(a[i - 1] - 1, a[i]) 的状态来使得严格单调递减的性质成立,但注意到我们不能把一个数削到负数,所以,一旦ai < 0说明该位即使削减到0也无法保证后半数组的单调递减了,故输出一个No, 否则就是Yes

代码
#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 10;
int a[N], n;
int main()
{
	int T;
	cin >> T;
	while(T--)
	{
		scanf("%d", &n);
		for(int i = 1; i <= n; i++)
		{
			scanf("%d", &a[i]);
		}
		if(n == 1 or n == 2 and a[1] != a[2])
		{
			printf("Yes\n");
			continue;
		}
		else if(n == 2 and a[1] == a[2] and a[1] == 0)
		{
			printf("No\n");
			continue;
		}
		int flag = 0, fl = 1;
		for(int i = 1; i <= n; i++)
		{
			if(!flag and a[i] < i - 1) flag = 1;
			if(flag)
			{
				a[i] = min(a[i - 1] - 1, a[i]);
				if(a[i] < 0){
					fl = 0;
					break;
				}
			}
		}
		if(fl) printf("Yes\n");
		else printf("No\n");
	}
	return 0;
}

C. Mind Control

题意:你和你的n - 1个朋友排成一列一起玩一个游戏,每次队头的一个人出队,然后拿走数组头部或尾部的一个数,你排在m位,你现在可以劝说至多k个朋友来让他们总是选择数组头部或者是数组尾部的数,而不受你控制的人随意选择(他们不一定会去选头尾当中最大的)
问你在各种情况下能拿到的最小的数的最大值是多少(有点绕)。

输入
4
6 4 2
2 9 2 3 8 5
4 4 1
2 13 60 4
4 1 3
1 2 2 1
2 2 0
1 2
输出
8
4
1
1
想法(官方题解)

你后面的人对你拿的数字并没有影响,因此k即使 >= m,能发挥作用的也只有你前面的m-1个人,不妨令 k <= m - 1。
为了让你能拿到的数尽可能大,轮到你时,你一定会选择数组头部和尾部的数字中的较大者
影响因素是:1)你选择让多少在你前面的人拿走数组头部的数字,用数字x表示
(拿走尾部的一样,只不过之后的表达略有不同,下同)
2)在你选定后游戏开始后,有多少个在你前面不受你控制的人选择了拿走头部的数, 用数y表示
(y的不同对应着每一个选择的“不同情况”,在x固定的情况下,不同的y能导致不同的结局)
由于题目数据量很小(n <= 3500),所以直接O(n^2)暴力都能过,直接暴力循环x,y即可
用线段树优化可以达到O(nlogn)

代码
 #include <bits/stdc++.h>
using namespace std;
int a[3600];
int main()
{
	int t;
	cin >> t;
	int n, m, k;
	while(t--)
	{
		scanf("%d %d %d", &n, &m, &k);
		for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
		int ans = -1e8;
		k = min(k, m - 1);
		for(int i = 0; i <= k; i++)
		{
			int casans = 1e9 + 10;
			for(int j = 0; j <= m - 1 - k; j++)
			{
				int tmp = max(a[1 + i + j], a[i + 1 + j + n - m]);
				casans = min(tmp, casans);
			}
			ans = max(ans, casans);
		}
		printf("%d\n", ans);
	}
	return 0;
} 

D. Irreducible Anagrams

题意:给出一个字符串s,q个查询,每个查询给出l,r
问s的第l个字符到r个字符构成的子串是否存在至少一个irreducible anagram(不可化简的同字母异序词)

irreducible anagram的定义如下:
一个字符串t是s的irreducible anagram,首先建立在它是s的anagram的基础上
对于一个字符串s,如果字符串t和s的区别只在于字母顺序,那么t和s就互为anagram(同字母异序词)

对于互为anagram的字符串s和t,如果能把s和t在同样的位置切开,分成k段(k >= 2),每一段si和ti也互为anagram,则称s 和 t互为 reducible anagram,反之,如果找不到这样的划分方法,则称t和s互为irreducible anagram

输入
aaaaa
3
1 1
2 4
5 5
输出
Yes
No
Yes
输入
aabbbbbbc
6
1 2
2 4
2 2
1 9
5 7
3 5
输出
No
Yes
Yes
Yes
No
No
想法

理由有时间补上,先上结论:一个字符串存在irreducible anagram,只需满足以下条件之一即可:
1)长度为1(切不出两段)
2)首尾字符不同(否则可以构造)
3)含有至少3种字符(这个就需要在处理查询之前处理好第i位前各种字符出现情况的表,然后借助前缀和的思想来完成
O(1)的查询)

代码
#include <bits/stdc++.h>
using namespace std;
const int  N = 2e5 + 10;
char s[N];
int cnt[N][26];
int main()
{
	scanf("%s", s);
	int q, l, r; 
	cin >> q;
	for(int i = 1; s[i - 1]; i++){
		memcpy(cnt[i], cnt[i - 1], sizeof(cnt[i - 1]));
		cnt[i][s[i - 1] - 'a']++;
	}
	while(q--)
	{
		scanf("%d %d", &l, &r);
		if(r - l + 1 == 1 or s[l - 1] != s[r - 1])
		{
			printf("Yes\n");
			continue;
		}
		int flag = 0, tmp[26] = {}, c = 0;
		for(int i = 0; i < 26; i++)
		{
			tmp[i] = cnt[r][i] - cnt[l - 1][i];
			if(tmp[i]) c++;
			if(c >= 3)
			{
				flag = 1;
				break;
			}
		}
		if(flag) printf("Yes\n");
		else printf("No\n");
	}
	return 0;
} 

E. Prefix Enlightenment

题意:给出编号从1到n的一排灯,每个灯有一个初始状态,或为开(1),或为关(0),给你k个这些灯的子集,保证任意三个子集的交集为空,你每次可以选择一个子集,然后改变这个子集内的所有灯的状态(开变关,关变开),请输出要使第I位的灯及之前的灯全为开,最少的操作次数(每个i都要输出一个答案),题目数据保证有解。

输入
7 3
0011100
3
1 4 6
3
3 4 7
2
2 3
输出
1
2
3
3
3
3
3
输入
8 6
00110011
3
1 3 8
5
1 2 5 6 7
2
6 8
2
3 5
2
4 7
1
2
输出
1
1	
1
1
1
1
4
4
输入
5 3
00011
3
1 2 3
1
4
3
3 4 5
输出
1
1
1
1
1
输入
19 5
1001001001100000110
2
2 3
2
5 6
2
8 9
5
12 13 14 15 16
1
19
输出
0
1
1
1
2
2
2
3
3
3
3
4
4
4
4
4
4
4
5
想法

首先要理解题目中给出的任意三个子集的交集为空,这句话翻译过来就是一个元素至多会在两个子集中出现。
对于每个集合,我们要么操作它,要么不操作它,于是我们就分别为每个集合构造两个点,一个代表选它,权值为1,一个代表不选它的,权值为0。
回到题目对每一位的操作,对为1的位,我们要么同时操作包含它的两个子集,(把这两个点连起来),要么同时不操作,最终的答案为这两者中较小者,对于为0的位,包含这个位的子集中只能选一个来操作,(把一个选,一个不选的点给连起来)。
注意有可能只有一个子集或者没有子集包含某个元素,所以要特判, 这里父亲节点为0代表不可行,只能选另外一种方案。

代码
#include <bits/stdc++.h>
using namespace std;
const int N = 3E5 + 10, MAX_N = 1e6 + 10;
char s[N];
int fa[MAX_N], w[MAX_N], se[N][2], n, k; 
int get(int x)
{
	if(x == fa[x]) return x;
	else return fa[x] = get(fa[x]);
}
inline int getcost(int x)
{
	int y = x <= k? x + k : x - k;
	int gx = get(x), gy = get(y);
	if(!gx) return w[gy];
	else if(!gy) return w[gx];
	else return min(w[gx], w[gy]);
}
inline void merge(int x, int y)
{
	int gx = get(x), gy = get(y);
	if(gy == 0) swap(gx, gy);
	fa[gy] = gx;
	if(gx) w[gx] += w[gy];
}
int main()
{
	scanf("%d %d %s", &n, &k, s);
	for(int i = 1; i <= k; i++) fa[i] = i, w[i] = 1;
	for(int i = k + 1; i <= 2 * k; i++) fa[i] = i, w[i] = 0;
	for(int i = 1; i <= k; i++)
	{
		int kazu, a;
		scanf("%d", &kazu);
		for(int j = 0; j < kazu; j++)
		{
			scanf("%d", &a);
			if(!se[a][0]) se[a][0] = i;
			else se[a][1] = i;
		}
	}
	int ans = 0;
	for(int i = 1; i <= n; i++)
	{
		if(!se[i][1])
		{
			int t = se[i][0];
			ans -= getcost(t);
			if(t != 0)
			{
				if(s[i - 1] - '0') fa[get(t)] = 0;
				else fa[get(t + k)] = 0;
			}
			ans += getcost(t);
		}
		else
		{
			int x = se[i][0], y = se[i][1];
			if(s[i - 1] - '0')
			{
				if(get(x) != get(y))
				{
					ans -= (getcost(x) + getcost(y));//这句话的意思是重置答案为0,之后合并后再从两个连通块里选较小者作为答案 
					merge(x, y);//都不选
					merge(x + k, y + k);//都选
					ans += getcost(x);
				}
			}
			else
			{
				if(get(x + k) != get(y))
				{
					ans -= (getcost(x) + getcost(y));
					merge(x + k, y);//一个选,一个不选
					merge(x, y + k);
					ans += getcost(x);
				}
			}
		}
		printf("%d\n", ans);
	}
	return 0;
} 

F. Coffee Varieties (easy version)

交互题,题意:镇上有n个咖啡馆,每个咖啡馆卖一种咖啡,你想知道一共有多少种。你可以通过询问你的朋友来确定到底有多少种咖啡,你每次可以发一个“? c” c为咖啡馆编号,来让他去喝,他会回答你在他喝过的
前k - 1杯中有没有和这次喝的咖啡种类一样的(Y or N)(最多问2* n ^ 2 / k 次)。你的朋友只会记得他最近喝过的K杯咖啡,超出范围的会遗忘,你也可以发一个“R”让他主动忘记之前喝过的所有咖啡(次数有限)。当你确定了答案以后,发一个“! s", s 为镇上的咖啡种类数。

不一定非得按以下的输入输出来,只要输出正确答案即可。

输入
4 2
N
N
Y
N
N
N
N
输出
? 1
? 2
? 3
? 4
R
? 4
? 1
? 2
! 3
输入
8 8
N
N
N
N
Y
Y
输出
? 2
? 6
? 4
? 5
? 2
? 5
! 6
想法

我们把n个咖啡馆按k / 2的大小(若k == 1,块大小为1)分为m块,每个编号1,2,3…m,按块1,块2,R, 块1,块3。。以此类推的顺序询问即可。
注意块内含有相同种类的情况。
顺带一提, hard version 能喝的次数更少,但可以注意到,按照上述方式我们问了很多次块1,但可以注意到,按块1,块2,后立刻询问块3,就相当于重置后询问了块2,块3内有无相同咖啡,这样就省掉了一些重复的询问。

代码
#include <bits/stdc++.h>
using namespace std;
vector<int> block[1050];
int ans, vis[1050];
inline void askblock(int x, int y)
{
	char s[5];
	for(int i = 0; i < block[x].size(); i++)
	{
		printf("? %d\n", block[x][i]);
		fflush(stdout);
		scanf("%s", s);
	}
	for(int i = 0; i < block[y].size(); i++)
	{
		printf("? %d\n", block[y][i]);
		fflush(stdout);
		scanf("%s", s);
		if(s[0] == 'Y' and !vis[block[y][i]]) vis[block[y][i]] = 1;
	}
}
inline void askblockinside(int x)
{
	char s[5];
	for(int i = 0; i < block[x].size(); i++)
	{
		printf("? %d\n", block[x][i]);
		fflush(stdout);
		scanf("%s", s);
		if(s[0] == 'Y') vis[block[x][i]] = 1;
	}
}
int main()
{
	int n, k;
	scanf("%d %d", &n, &k);
	int size = k == 1? 1 : k / 2;
	for(int i = 1; i <= n; i++) block[(i - 1) / size + 1].push_back(i);
	for(int i = 1; i <= (n - 1) / size + 1; i++)
	{
		askblockinside(i);
		printf("R\n");
		fflush(stdout);
		for(int j = i + 1; j <= (n - 1) / size + 1; j++)
		{
			askblock(i, j);
			printf("R\n");
			fflush(stdout);
		}
	}
	for(int i = 1; i <= n; i++) if(!vis[i]) ans++;
	printf("! %d\n", ans);
	fflush(stdout);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值