2020 JUSTCTF 算法题解

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]的情况!即:ababab121212等等!

的情况!

  • 到这里,我们的思路就很清晰了:就是要求仅仅由两个字符组成的交替字符串出现最大轮回次数了。

标程

#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值