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

353

被折叠的 条评论
为什么被折叠?



