【C】张梁计算器

【项目介绍】

张梁是sp包的武将,其有一项技能“方统”,需要计算一系列牌的点数,使之和为36,然后便可对敌方造成大量伤害,具体信息可参考百科,想必点进来的都是懂张梁作为大数学家的含金量的😜,本项目就是为了研究一个算法,快速计算所有的炸人方案
什么是“大数学家”啊?

【代码展示】

引言:以大雁的这篇张梁视频跳转到1:00为例
张梁牌区所有点数为:2、12、13
“方”区所有点数为:6、7、12、12

一、函数介绍

  1. 全局变量
#define BOOM 36	//懂的都懂
// 牌区,空白处全为0
int PaiQ[20]={2,12,13};
// “方”区
int Fang[20]={6,7,12,12};
// 用于记录应当排除的“方”的下标,空白处赋为-1
int record[20];
int Fsum=0;         //“方”区点数和
int Flen=0;         //“方”区牌数
  1. 次要函数
/*将传入的数组初始化为value*/
void init(int* Array,int value)
{
    for(int i=0;i<20;i++) Array[i]=value;
}

/*将数组Array视作队列压入新元素*/
void push(int* Array, int value, int newp)
{
    // 令新元素newp替换第一个空白处(值为value)
    for(int i=0;i<20;i++){
        if(Array[i]==value){
            Array[i]=newp;
            break;
        }
    }    
}

/*将数组Array视作队列弹出旧元素*/
void pop(int* Array,int oldp)
{
    for(int i=0;i<20;i++){
        // 找到与oldp同值的元素后,标记下标i
        // 将后面所有元素依次往前推一位,直到遇到空白处
        if(Array[i]==oldp){
            for(;i<20;i++){
                if(Array[i]==0) break;
                Array[i]=Array[i+1];
            }
            break;
        }
    }
}
  1. 主要函数,各语句含义见注释
/*递归求解炸人方案,由总和倒推到单个元素(实际上推不到,最少必须有2张“方”才能炸人),
本质上是组合数的思想,fsum是子全“方”和,tag是即将减去的元素的下标,calC是计算组合数的意思*/
int calC(int fsum, int PaiQ_i, int tag){
    fsum = fsum - Fang[tag];
    // 这里与canBoom里判断的思想是一样的
    if(fsum + PaiQ_i < BOOM) return 0;
    else if(fsum + PaiQ_i == BOOM){
        init(record,-1);    //算出了炸人方案,表示要记录元素了,先初始化一下record
        printf("可以炸人!方案为:");
        return 1;
    }
    else if(fsum + PaiQ_i > BOOM){
        while(++tag<Flen){  //++tag表示tag+1后的值,这里图省字了
            if(calC(fsum, PaiQ_i, tag)==1){
                // 因为我是递归地减去元素的,而要输出的是剩下的元素,
                // 所以需要先记录每一步删去的是什么元素,再统一输出剩余的元素
                push(record,-1,tag);
                return 1;
            }
        }
    }
}

/*输出解决方案*/
void printScheme(int PaiQ_i)
{
    // 此时record里记录的是应当删去的元素下标
    // 这里采取的方法是遍历record,将Fang(先用tmp复制)里对应下标的元素赋值为-1
    // 然后遍历tmp,输出剩余元素
    int tmp[20]={0};
    memcpy(tmp,Fang,20);
    for(int i=0;i<20;i++){
        if(record[i]==-1) break;
        tmp[record[i]]=-1;
    }
    for(int i=0;i<20;i++){
        if(tmp[i]==-1) continue;
        if(tmp[i]==0) break;
        printf("%d ",tmp[i]);
    }
    printf("+ 点数为%d的牌。\n",PaiQ_i);
}

/*判断是否可以炸人*/
void canBoom(){
    init(record,-1);    //初始化记录表
    // 计算“方”区的点数和
    for(int i=0;i<20;i++){
        if(Fang[i]==0) break;
        Fsum += Fang[i];
        Flen++;
    }
    // 遍历牌区,抽出每一张牌,判断是否能炸人
    for(int i=0;i<20;i++){
        if(PaiQ[i]==0) break;   //遇到牌区空白处,停止遍历
        // 以下对应三种情况:
        // 1.全部“方”和该牌点数的和都不够36,就不必算“方”的组合情况了
        // 2.全部“方”和该牌点数的和正好为36,直接输出,不必再算
        // 3.全部“方”和该牌点数的和超过36,需算“方”的组合情况,循环使用递归函数
        if(Fsum + PaiQ[i] < BOOM){
            printf("所有“方”与点数为%d的牌的和不满36,无炸人解;_;\n",PaiQ[i]);
        }
        else if(Fsum + PaiQ[i] == BOOM){
            printf("所有“方”与点数为%d的牌的和正好为36,刚好炸^0^\n",PaiQ[i]);
        }
        else if(Fsum + PaiQ[i] > BOOM){
            int tag=0;          //下标
            boolean ret=FALSE;  //结果标记
            while(tag<Flen){    //从tag=0开始,传入sumC的是原tag,算完后tag+1
                if(calC(Fsum, PaiQ[i], tag++)==1){
                    ret=TRUE;   //表示可以炸人,不输入失败语句
                    push(record,-1,tag-1);
                    printScheme(PaiQ[i]);
                    init(record,-1);
                    continue;   //算出炸人方案就不必再往下递归了,之后向同级或往上递归
                }
            }
            if(!ret) printf("很遗憾,点数为%d的牌没有炸人方案:(\n",PaiQ[i]);
        }
    }
}

二、主程序

int main(){
    int newcard=0;
    printf("分析炸人方案中...\n");
    canBoom();
    printf("计算结束!\n");
    return 0;
}

三、效果展示

在这里插入图片描述在这里插入图片描述
与视频中所展示的方案一样

再以视频中1:48为例:
张梁牌区:1、2、6、12、13
“方”区:6、8、9、10
在这里插入图片描述在这里插入图片描述在这里插入图片描述
得出的结论一致,而且额外算出一解

四、结论与问题

这里主要用的算法是循环套递归,目的是模拟组合数的计算,张梁的炸人规则要求对牌区(设为有 n 1 n_1 n1张牌)的每一张牌进行判断,也就是循环 n 1 n_1 n1次;
然后对方区(设有 n 2 n_2 n2张牌)计算所有组合数,也就是计算 2 n 2^n 2n-1 次

牌区: n 1 n_1 n1
方区: C n 2 1 + C n 2 2 + C n 2 3 + . . . + C n 2 n 2 = 2 n 2 − 1 C_{n_2}^{1}+C_{n_2}^{2}+C_{n_2}^{3}+...+C_{n_2}^{n_2}=2^{n_2}-1 Cn21+Cn22+Cn23+...+Cn2n2=2n21
总共计算次数: n 1 × ( 2 n − 1 ) n_1\times(2^n-1) n1×(2n1)

总的来说,根据规则,时间复杂度“必然”是要达到 O( 2 n 2^n 2n)的,甚至是指数 × \times ×线性,可见复杂度非常高,当然也可以根据规则减少一些枚举操作:

  1. 牌区的牌 + + +若干方的和 ≤ \leq 36,不必深入计算
  2. 方区最少要抽出2张方,不必考虑1张方(牌点数最大只有13,1张方太少)的情况。
  3. 值相等的牌避免多算、值相等的方避免重复组合 (比较麻烦,没有研究)

由于本人才疏学浅,不太清楚有没有解决这种穷举组合数问题更好的算法,不知是否有高手能在评论区予以指点?不过我自认为我这种计算方法应该是比较简单的,因为契合组合数的计算方式,基本没有对同一情况的重复计算。
其他问题还有就是不太方便,需要将已有的牌区牌和方区的点数全部输入程序再计算,只能应用于事后分析,不能用于实战,博主日后还会研究如何在实战中实时地展示炸人方法,也就是类似于插件那样的程序或app。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值