Comet OJ - 2019六一欢乐赛

博客围绕宝可梦相关题目展开。一是从多只皮卡丘中选后背数字两两互质的最多数量,可通过递归或枚举子集求解;二是判断一个字符串删减两个字符后能否变成另一个字符串,可双字符串遍历或用特定思维判断。

跟随虾哥项目实践,硬件选小智就对了

xiaozhi 开源方案官方适配,二次开发文档齐全

第002话 宝可梦中心大对决!
时间限制:1000ms
空间限制:256MB

题目描述:

在大木博士那里,小智发现推荐的三只宝可梦都已经被別人選走了,最终他挑选了皮卡丘作为自己的第一只宝可梦。皮卡丘不是很待见小智,但还是在危急之时救了小智一命,小智和皮卡丘的关系也因此熟络了许多。皮卡丘因为救小智受了重伤,小智急忙带着皮卡丘去常磐市的医院治疗。

常磐市经常发生绑架宝可梦的事件,犯人是一男一女,他们这天也来到了常磐市的治疗中心,准备劫走治疗中心里面罕见的宝可梦。用"华丽"的方式登场后,将他们的宝可梦瓦斯弹和阿柏蛇放出来,开始攻击和搜寻每一间房间,并且把电源给切断了。

小智、小霞和护士姐姐迅速把趟在病床上的皮卡丘推进传送室,停电后,护士姐姐说他们有备用电源,小智放眼一看,正在发电的居然是一群训练有素的皮卡丘!小智发现,每一个皮卡丘后背都有一个数字,护士姐姐解释说:“皮卡丘集中发电的能力跟他们后背的数字有关,当上场发电的皮卡丘们后背的数字任两个数字都互质的话,他们的发电能力就会强大无比!当然,同样都是互质的情况下,上场的皮卡丘数量越多越好。”

不管是战斗,还是发电,我们都需要皮卡丘!现在,已知共有 nn 只皮卡丘,给出每只皮卡丘后背的数字,请你选择尽量多的皮卡丘上场,使得它们后背的数满足任意两个数字互质。只需输出你最多能选择了多少只皮卡丘。

注:两个数字互质当且仅当他们没有 11 以外的正公因数。

在这里插入图片描述

输入描述:
输入有多组数据,第一行输入一个正整数 T (T <= 10) ,表示测试组数。
然后对于每组数据,第一行有一个正整数 n(n <= 14),第二行有 n 个两两相异的数
在这里插入图片描述
代表这 nn 只皮卡丘后背的数字。

输出描述:
对于每组数据,输出一个正整数表示最多可以选几只皮卡丘,使得任两只皮卡丘后背的数字都互质。

样例输入 1

2
3
3 4 5
7
7 6 5 4 3 2 1

样例输出 1

3
5

提示

对于样例中第一组数据,可以三只皮卡丘同时上场,因为他们后背上的数字两两互质。

对于样例中第二组数据,我们可以选后背号码分别为 7,5,3,2,1 的 5 只皮卡丘。

题意:
给定n(n<=14)个数,求最多有几个数,使得所选的数两两互质。

解析:
本题n<=14,所以14个数选或不选共有2^14=16384种可能,于是我们可以枚举所有答案,找最大值即可。

枚举所有子集有两种方法:

1.递归

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define ms(s) memset(s, 0, sizeof(s))
const int inf = 0x3f3f3f3f;
const int maxn = 14;
int a[maxn];
int gcd(int x, int y){while(y^=x^=y^=x%=y);return x;}//也可以直接用__gcd,头文件是#include <algorithm>,在我的电脑跑报错orz
bool used[maxn];
int T,ans,n,cnt;//ans表示最多的质数个数

void dfs(int x){
	if(x == n){
		cnt = 0;
		for(int i=0; i<n; ++i){
			for(int j=0; j<i; ++j){
				if(used[i] && used[j] && gcd(a[i],a[j]) != 1) return;
			}
		}
		for(int i=0; i<n; ++i){
			if(used[i]) cnt++;
		}
		ans = max(ans,cnt);
		return;
	}
	used[x] = true;
	dfs(x+1);
	used[x] = false;
	dfs(x+1);
}

int main(int argc, char * argv[]) 
{
    cin >> T;
    while(T--){
    	cin >> n;
    	ms(a);
    	ms(used);
    	ans = 0;
    	for(int i=0; i<n; ++i){
    		cin >> a[i];
    	}
    	dfs(0);
    	cout << ans << endl;
    }
    return 0;
}
/*
2
3
3 4 5
7
7 6 5 4 3 2 1
*/

2.使用数字0~2^n-1来代表所有子集:
使用以下方法来把x对应到一个子集:
把一个数写成二进位,并补齐到n位数,例如:在这里插入图片描述
那么若x由右边数来第i位若是1,就表示x对应到的子集有包含第i个数,反之,则不包含。
所以,5对应到的子集包含第一个数和第三个数,而11对应到的子集包括第1,3,4个数。

代码

#include<cstdio>
#include<algorithm> // 要使用 __gcd 必须使用 #include <algorithm>
using namespace std;
const int MAX_N = 14;
int gcd(int x,int y) {while(y ^= x ^= y ^= x %= y); return x;}
int a[MAX_N];
// 此函式能获得 x 从最低位数来第 i 位的数字(位数从 0 开始编号)
int get_bit(int x, int i) {return (x >> i) & 1;}
// 检查 x 对应到的⼦子集是否满⾜足任两数都互质
bool coprime_in_subset(int n, int x){
	for(int j = 0; j < n; j++) {
		for(int k = 0; k < j; k++) {
			if(get_bit(x, j) && get_bit(x, k)) {
				if(gcd(a[j], a[k]) != 1) return false;
			}
		}
	}
	return true;
}
void solve_one_test() {
	int n;
	int an = 0;
	scanf("%d", &n);
	for(int i = 0;i < n; i++) scanf("%d", &a[i]);
	for(int i = 0;i < (1<<n); i++){
		if(coprime_in_subset(n,i)){
			int cnt = 0;
			for(int j = 0;j < n; j++){
				cnt += get_bit(i,j);
			}
			if(an < cnt) an = cnt;
		}
	}
	printf("%d\n",an);
}

int main() {
	int T; // 储存有⼏几组数据
	scanf("%d",&T);
	for(int _t = 1; _t <= T; _t++) {
		solve_one_test();
	}
}

时间复杂度:
由于总共有2^n个集合,对于每个集合,要枚举任两个数字是否互质(O(n^2)个组合),计算是否互质的方法是采用求最大公因数是否为1,这部分的时间复杂度是O(logMAX_N),故时间复杂度是O(2^n*n^2*logMAX_N)

下还有一种大佬做法:
递归+预处理+位元压缩(2^n)

#include<cstdio>
#include<algorithm>
using namespace std;
const int MAX_N = 14;
int n;
int a[MAX_N];
int coprime[MAX_N];
int an;
void dfs(int id, int cnt,int choosen_mask){
	if(id == n){
		if(an < cnt) an = cnt;
		return;
	}
	if((coprime[id] & choosen_mask) == choosen_mask)
		dfs(id + 1, cnt + 1, choosen_mask | (1 << id));
	dfs(id + 1, cnt, choosen_mask);
}
void pre_do(){
	for(int i = 0; i < n; i++) {
		coprime[i] = 0;
		for(int j = 0; j < n; j++) {
			if(i != j && __gcd(a[i] , a[j]) == 1)
			coprime[i] |= 1 << j;
		}
	}
}
int main() {
	int T;
	scanf("%d", &T);
	for(int i = 1; i <= T; i++) {
		an = 0;
		scanf("%d", &n);
		for(int j = 0; j < n; j++) scanf("%d", &a[j]);
		pre_do();
		dfs(0, 0, 0);
		printf("%d\n",an);
	}
	return 0;
}

第003话 收服宝可梦吧!

题目描述:
继上一话皮卡丘把那几个坏人炸飞之后,小智确认皮卡丘已经完全恢复了。于是带着皮卡丘继续上路,前往尼比市去,小霞为了她的脚踏车的事情也一直跟着小智。

在通往尼比市的路上,要经过一个漆黑的常磐森林,小智信心满满,他相信这个森林一定能抓到很多宝可梦,正走着,小霞突然害怕地双手扑到小智的背上,小智侧头一看,咦,这不是可爱的虫系宝可梦——绿毛虫吗? 小智向前走一步,撇开衣角摸出挂在皮带上的精灵球,决定将这条绿毛虫收服了。说时迟那时快,小智将精灵球精准地砸中绿毛虫的头部,绿毛虫瞬间被收进了精灵球里面。

虽然小霞非常讨厌虫,但是小智作为一个不挑食、啥都喜欢的好孩子,对绿毛虫可算是非常喜欢,而且这也是他收服的第一个宝可梦,是迈向成功的一大步!对绿毛虫喜欢的不仅仅是小智,皮卡丘也开心的喊着"皮卡皮卡!",终于有同类一起生活了!

到了夜晚,小智和小霞都各自钻进自己的睡袋里睡觉了,皮卡丘和绿毛虫却在一块大石头上享受月光浴,他们开心地交流着…

实际上,不同宝可梦之间的对话是有规则的。假设绿毛虫说出的话是字符串 s ,皮卡丘需要先说一个字符串 t , t 是 s 删掉两个字符之后得到的字符串,他才能获得绿毛虫要传达的信息,绿毛虫面对皮卡丘说的话也是一样的规则。

那么,给出两个字符串 s 和 t ,你能判断 t 是不是 s 删掉两个字符后的字符串吗?

输出描述:
输入有多组数据,第一行输入一个正整数 T
在这里插入图片描述
,表示测试组数,然后对于每组数据,第一行有一个字符串 s ,第二行有一个字符串 t。数据中所有字符串都是由英文小写字母’a’至’z’组成的,长度都不是 0,且所有字符串的总长度和不超过10^6。

输出描述:
对于每组数据各输出一行,如果是,输出数字 1,如果不是,输出数字 0。

样例输入 1
3
abcde
ace
abcde
aed
abcde
abcd

样例输出 1
1
0
0

提示

对于样例中第一个数据对于样例中第一个数据,可以把字符串 “abcde” 删掉第 2 个和第4 个字符来得到字符串 “ace”,故要輸出 1。

对于样例中第二和第三个数据,删掉 22 个字符后可能得到的结果有 “abc”、“abd”、“abe”、“acd”、“ace”、“ade”、“bcd”、“bce”、“bde”、“cde” 这 1010 种,并无法产生 “aed” 或 “abcd”,所以都要輸出 0。

题意:
第一个字符串删减两个字符后变成第二个字符串。

思路:
两个字符串一起遍历,当出现两个字符时num–,最后根据num的情况讨论即可。

//比赛的时候因为T组数据每组都memset和每次循环都strlen导致超时了orz

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define ms(s) memset(s, 0, sizeof(s))
const int inf = 0x3f3f3f3f;
const int maxn = 1e+6+100;
char s1[maxn],s2[maxn];

int main(int argc, char * argv[]) 
{
    int T;
    scanf("%d",&T);
    while(T--){
    	scanf("%s%s",s1,s2);
    	int len1 = strlen(s1);
    	int len2 = strlen(s2);
    	int num = 2,i,j;
    	if(len1 - len2 != 2){
    		puts("0");
    		continue;
    	}
    	for(i=0,j=0; i<len1&&j<len2;){
    		if(s1[i] == s2[j]) i++,j++;
    		else num--,i++;
    	}
    	if(num < 0){
    		puts("0");
    	}
    	else puts("1");
    }
    return 0;
}

此题还有另外一种思想,记第一个字符串为s,第二个长度为t,判断s和t长度的差是否为2以及s和t的LCS长度是否为t的长度,但是LCS会超时,以下为另一种思维:

令Sprefix[i]代表s长度为i的前缀,Tprefix[i]同理。
使用dp[i][j] = true 代表Sprefix[i]是否拿移除j个字符后变成Tprefix[i-j],否则dp[i][j] = false。
关系式为:dp[i][j] = true 仅当(dp[i-1][j] == true 且 Si = Ti-j) 或(j > 0且dp[i-1][j-1] == true)
这是因为Sprefix[i]若能移除j个字符变成Tprefix[i-j],那么可分为以下两种情况:
1.Sprefix[i]的最后一个字符没被移除,那么必须dp[i-1][j] = true 且 s[i] = t[i-j]2.Sprefix[i]的最后一个字符被移除,那么Sprefix[i-1]必须能移除j-1个字符后变成Sprefix[i-j]。
题目所求为dp[length(s)][2],因此我们只要依序计算出dp[1...length(s)][0...2]即可。

代码如下:

#include<cstdio>
#include<cstring>
const int SIZE = 1000010;
char s[SIZE];
char t[SIZE];
int dp[SIZE][3];
void solve(){
	scanf("%s%s", s + 1, t + 1);
	int n = strlen(s + 1);
	int m = strlen(t + 1);
	if(m != n - 2){
		puts("0");
		return;
	}
	for(int i = 1;i <= n; i++) {
		for(int j = 0;j <= 2; j++) dp[i][j] = false;
	}
	dp[0][0] = true;
	for(int i = 1;i <= n; i++){
		for(int j=0;j<=2;j++){
			if(dp[i-1][j] && s[i] == t[i-j]) dp[i][j] = true;
			if(j > 0 && dp[i-1][j-1]) dp[i][j] = true;
		}
	}
	if(dp[n][2]) puts("1");
	else puts("0");
}
int main() {
	int T;
	scanf("%d", &T);
	while(T--) {
		solve();
	}
}

跟随虾哥项目实践,硬件选小智就对了

xiaozhi 开源方案官方适配,二次开发文档齐全

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值