JUSTCTF 算法题解
小汪钓鱼
这题还是挺有意思的,谁知道网上还有源码,这波大意了啊,没有验题(逃)!
出题人背锅o( ̄┰ ̄*)ゞ
题目概述
你听说过小猫钓鱼吗?那你一定能会做小汪钓鱼!
两个人一开始各有一摞牌,然后轮流着拿自己那一摞的最顶上的牌,一张接着一张放到公共的牌池里,如果遇到之前有和这一张上的数字相同的牌,就把这两张牌包括起来的所有牌都放到自己牌堆的底部(拿取的顺序为从刚刚放的那张牌开始,一张一张放到自己牌堆的底部,直到拿掉以前那张数字相同的为止)。
小猫在和小汪正在玩这个游戏,可是随着牌的数量增多,游戏进行的时间也会越来越长,心急的小汪等不了如此长的时间,于是想想你求助,尽快知道结果,当有一方手里没牌时,游戏结束。
小汪一开始手中的牌为:6 5 4 3 2 1 8 7 5 2 3 5 6 9 8 2 1 4 6 2 7 8 8 6 5
小猫一开始手中的牌为:1 2 3 4 5 6 7 8 9 9 8 7 6 5 4 3 2 2 1 5 6 3 2 1 1
小汪先出牌。
你能帮帮他吗?flag为最后赢家牌堆中的所有牌,请从牌堆顶开始往牌堆底列。
题目分析
- 很明显只要模拟题目所给的过程就行了,也没有什么简单的解法。
- 你可以选择和另一个人使用纸片模拟,经过计算,只要两千多步就能模拟完整个过程。也可以选择使用程序计算出来,一下给出使用c++编写计算程序的方法,详细的步骤都以注释的方式给明了。
标程
#include<stack>
#include<queue>
#include<cstring>
#include<iostream>
#include<algorithm>
#define x first
#define y second
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
int main() {
//使用队列来模拟一开始两个人手中的牌
queue<int> dog, cat;
//n代表输入的牌数
//这边只需要数一下就行了
int n, op;
cin >> n;
for(int i = 0; i < n; i ++ ) {
cin >> op;
dog.push(op);
}
for(int i = 0; i < n; i ++ ) {
cin >> op;
cat.push(op);
}
//使用idx来判断当前是谁的回合
int idx = 1;
//使用栈来模拟牌堆
stack<int> stk;
//使用st来记录是否牌堆中有相同的牌
bool st[10];
memset(st, false, sizeof st);
while(dog.size() && cat.size()) {
//如果idx为奇数,那就是小狗的回合
if(idx & 1) {
op = dog.front();
dog.pop();
//如果之前牌堆里面有op 就把那张牌到当前牌放到小狗牌库里面
if(st[op]) {
st[op] = false;
dog.push(op);
while(stk.top() != op) {
st[stk.top()] = false;
dog.push(stk.top());
stk.pop();
}
//最后将op压入栈
dog.push(stk.top());
stk.pop();
} else {
//如果之前没有op,那直接压入栈即可
stk.push(op);
st[op] = true;
}
} else {
//对于小猫也是相同的操作
op = cat.front();
cat.pop();
if(st[op]) {
st[op] = false;
cat.push(op);
while(stk.top() != op) {
st[stk.top()] = false;
cat.push(stk.top());
stk.pop();
}
cat.push(stk.top());
stk.pop();
st[op] = false;
} else {
stk.push(op);
st[op] = true;
}
}
idx ++ ;
}
while(cat.size()) {
cout << cat.front() << ' ';
cat.pop();
}
while(dog.size()) {
cout << dog.front() << ' ';
dog.pop();
}
return 0;
}
/*
输入数据:
25
6 5 4 3 2 1 8 7 5 2 3 5 6 9 8 2 1 4 6 2 7 8 8 6 5
1 2 3 4 5 6 7 8 9 9 8 7 6 5 4 3 2 2 1 5 6 3 2 1 1
*/
好家伙
这题本来是怕你们做第一题太难,然后特意准备的简单题。但是没想到第一题倒这么快,这道题只能提前挑起大梁了,还好表现不错。
其实这题是不难的,只要多列几组数据,基本上就能找到规律。
题目描述
当我们认为一个东西比较nice时,我们会在它前面加一个好字,来表示对它的赞美。比如当我们认为一个人比较nice时,我们会称ta为好家伙,或者当我们碰到一件事情比较nice时,我们也会发自内心地感叹一声:好家伙。
现在你遇到了一个字符串(该字符串因为体型过于庞大,现在被存放在string.txt里面了)
好奇的你想知道,它到底是不是个好家伙!
我们对好字符串的定义是:对它作左变换和右变换,得到的结果一样。
- 左变换的定义为:将该串的第一个字符拿出,并拼接到串的末尾,举个栗子:如果我们对12345进行左变换,得到的结果为:23451。
- 相对的,右变换的定义为:将该串的最后一个字符拿出,并拼接到串的开头,举个栗子:如果我们对12345进行右变换,得到的结果为:51234。
很显然,你遇到的字符串并不是一个nice的字符串,但是,热心的你准备帮它一把,使得它变成一个好家伙。
虽然,你并不能无中生有,变出新的字符。
但是,你可以从字符串中拿掉某些字符,让它变得更为简短,当你拿掉字符的时候,后面的字符会自动将前面的空补齐。
举个栗子:原字符串为12345。当你拿掉字符3时,字符串就 变为:1245。
现在请问:你最少需要从原来的字符串中剥离多少个字符,才能让他变成一个好家伙!
题目分析
-
题目中要求最少剥离的个数,相当于求最长的成立的个数,也就是最长的保留的个数,即:所给字符串的子序列所组成的最长
好字符串
长度为多少! -
那我们就要搞清楚好字符串有什么特性!
-
由题意可得,左变换将除了第一个之外的所有元素都向前移了一位,此步对应的过程为:
a'[i] = a[ i + 1] && a'[n] = a[1]
,右变换将除了最后一个元素之外的所有元素向后移动了一位,此步对应的过程为:a'[i] = a[i - 1] && a'[1] = a[n]
。 -
题目要求左变换等于右变换的情况,由以上四个公式可得,就是要求:
a[i] == a[i + 2]
,同时满足a[1] == a[n - 1]
的情况!即:ababab
,121212
等等!
的情况!
- 到这里,我们的思路就很清晰了:就是要求仅仅由两个字符组成的交替字符串出现最大轮回次数了。
标程
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 200010;
char s[N];
int cnt[10][10];
bool st[10][10];
int main() {
//freopen是打开文件的指令,第一个参数是路径,这边直接放在当前文件夹下就行了。
freopen("string.txt", "r", stdin);
memset(cnt,0,sizeof cnt);
memset(st,false,sizeof st);
scanf("%s",s);
//求出数组长度
int len=strlen(s);
//遍历所有元素
for(int k=0;k<len;k++) {
int op=int(s[k]-'0');
//面对当前元素,计算与其他所有元素的组合
for(int i=0;i<=9;i++) {
if(!st[op][i]) {
st[op][i]=true;
cnt[op][i]++;
}
if(st[i][op]) {
st[i][op]=false;
cnt[i][op]++;
}
}
}
int res=0;
//最后遍历所有组合,求出最大组合数的情况
for(int i=0;i<10;i++) {
for(int j=0;j<10;j++) {
if(i==j) {
res=max(res,cnt[i][j]/2);
}
else {
res=max(res,(cnt[i][j]>>1)<<1);
}
}
}
//res就是要保留的长度,使用总长度减去他,就是最少要削减的长度
printf("%d\n",len-res);
return 0;
}