来自玄学的剪枝

这道题的剪枝应该可以作为搜索中剪枝学习的一个典型案例,做完这题,深刻的感受到了剪枝的玄学

题目链接:https://daniu.luogu.org/problemnew/show/1092

题目大意:给出一个N进制的加法算式(N最大为26),在这个加法算式中,数字都用大写字母表示,相同的数字用相同的字母表示,且给出的几个数中包含了表示0-N的所有字母。需要求解的是A-N表示的数字是多少。

剪枝思路

这个平台在提交的时候,一共有10个测试样例,每个10分,然后就开始了艰难的AC之旅

初始思路

由于测试样例给出的是A-E的五进制计算,所以,一下子想到的是根据A-E来进行搜索,也就是计算出A-E中的各个全排列方式,对于每一个全排列的方式,判断是否满足这个式子,如果满足,这输出该排列。

然后就有了如下代码

#include <iostream>
using namespace std;
int n,dp[26];
bool book[26],flag = 0;
char ch1[26],ch2[26],ch3[26];
int check() {
    int weight = 0,ff = 1;
    for (int i=n-1; i>=0; i--) {
        int sum = dp[ch1[i]-'A'] + dp[ch2[i]-'A'] + weight;
        weight = sum/n;
        sum = sum % n;
        if (dp[ch3[i]-'A']!=sum){ 
            ff = 0;
            break;
        }
    }
    return ff;
}
void dfs(int step) {
    if (flag) return;
    if (step==n) {
        if (check()) {
            flag = 1;
            for(int i=0; i<n; i++) cout<<dp[i]<<" ";
        }
        return ;
    }
    for (int i=n-1; i>=0; i--) {
        if (!book[i]) {
            book[i] = 1;
            dp[step] = i;  
            dfs(step+1);
            book[i] = 0;
        }
    }
}
int main(){
    cin>>n;
    for (int i=0; i<n; i++) cin>>ch1[I];
    for (int i=0; i<n; i++) cin>>ch2[I];
    for (int i=0; i<n; i++) cin>>ch3[I];
    dfs(0);
    return 0;
}

然后30分。也就是说只能过N<10的测试样例。

想想也是,26位的全排列,肯定是炸的,然后开始剪枝优化

剪枝优化

既然直接给出A-E的全排列再去判断会超时,那为什么不边给出全排列边判断呢。比如给出的ABCED+BDACE=EBBAA。那么就可以得到(D+E)%5=A,在进行全排列的过程中,如果不满足这个条件,就不需要再继续搜索下去了。

既然要边排列边判断,那肯定不能进行A-E的依次搜索。按照上面的思路,我们要先给DEA赋值,然后判断此值满不满足,如果满足,再给左边这一列ECA赋值。对于前面已经赋值过得字母,可以不用进行搜索,直接dfs(step+1)即可

具体实现就是,把给出的3段字母串,串成一串长串(最多78位),如上A-E所示的例子中,第一个ABCED所在的下标分别为:0,3,6,9,12

这样的赋值就可以吧这个长串作为搜索对象,直接进行搜索,在搜索的过程中,每搜索层数+3,就进行一次判断。

于是就有了如下代码:

#include <iostream>
#include <string.h>
using namespace std;
int n,book[26],all[78],dp[26],flag = 0;
char ch];
int check(int step) {
    int weight = 0;  
    for (int i=0; i<step; i+=3) {
        int sum = dp[all[i]] + dp[all[i+1]] + weight;
        weight = 0;
        if (sum>=n) weight = 1;
        sum = sum % n;
        if (dp[all[i+2]]!=sum)  return 0;
    }
    return 1;
}
void dfs(int step) {
    if (flag) return;
    if (step%3==0&&!check(step)) return;
    if (step==3*n) {
        for (int i=0; i<n; i++)  cout<<dp[i]<<" ";
        flag = 1;
        return;
    }
    if (dp[all[step]]==-1) {
        for (int i=n-1; i>=0; i--) {
            if (!book[i]) {
                book[i] = 1;
                dp[all[step]] = I;
                dfs(step+1);
                book[i] = 0;
                dp[all[step]] = -1;
            }
        }
    } else dfs(step+1);
}
int main(){
    cin>>n;
    memset(dp,-1,sizeof(dp));
    for (int i=0; i<n; i++) {
        cin>>ch;
        all[3*(n-1-i)] = ch - 'A';
    }
    for (int i=0; i<n; i++) {
        cin>>ch;
        all[3*(n-1-i)+1] = ch - 'A';
    }
    for (int i=0; i<n; i++) {
        cin>>ch;
        all[3*(n-1-i)+2] = ch - 'A';
    }
    dfs(0);
    return 0;
}

注意上面有一行。if (dp[all[step]]==-1) {,这是用来判断这个字母是否已经被赋值,如果被赋值就直接进入下一层的搜索即可,但是一定要加else

这个代码。90分

WTF,优化也优化了,剪枝也剪枝了,你还要我怎样。。

无奈的看了大神的思路之后。。。我表示真的没话讲,玄学剪枝

AC代码

#include <iostream>
#include <string.h>
using namespace std;
int n,book[26],all[78],dp[26],flag = 0;
char ch;
int check(int step) {
    int weight = 0;  
    for (int i=0; i<step; i+=3) {
        int sum = dp[all[i]] + dp[all[i+1]] + weight;
        weight = 0;
        if (sum>=n) weight = 1;
        sum = sum % n;
        if (dp[all[i+2]]!=sum) return 0;
    }
    return 1;
}
int checkFront(int step) {
    for (int i=3*n-1; i>step; i-=3) 
        if (dp[all[i-2]]>=0&&dp[all[i-1]]>=0&&dp[all[i]]>=0&&dp[all[i]]!=(dp[all[i-1]]+dp[all[i-2]])%n&&dp[all[i]]!=(dp[all[i-1]]+dp[all[i-2]]+1)%n)
            return 0;    
    return 1;                                                                                                                                                          
}
void dfs(int step) {
    if (flag||!checkFront(step-1)) return;
    if (step%3==0&&!check(step)) return;
    if (step==3*n) {
        for (int i=0; i<n; i++) cout<<dp[i]<<" ";
        flag = 1;
        return;
    }
    if (dp[all[step]]==-1) {
        for (int i=n-1; i>=0; i--) 
            if (!book[i]) {
                book[i] = 1;
                dp[all[step]] = I;
                dfs(step+1);
                book[i] = 0;
                dp[all[step]] = -1;
            }
    }           
    else dfs(step+1);
}
int main(){
    cin>>n;
    memset(dp,-1,sizeof(dp));
    for (int i=0; i<n; i++) {
        cin>>ch;
        all[3*(n-1-i)] = ch - 'A';
    }
    for (int i=0; i<n; i++) {
        cin>>ch;
        all[3*(n-1-i)+1] = ch - 'A';
    }
    for (int i=0; i<n; i++) {
        cin>>ch;
        all[3*(n-1-i)+2] = ch - 'A';
    }
    dfs(0);
    return 0;
}

玄学代码

int checkFront(int step) {
    for (int i=3*n-1; i>step; i-=3) 
        if (dp[all[i-2]]>=0&&dp[all[i-1]]>=0&&dp[all[i]]>=0&&dp[all[i]]!=(dp[all[i-1]]+dp[all[i-2]])%n&&dp[all[i]]!=(dp[all[i-1]]+dp[all[i-2]]+1)%n)
            return 0;    
    return 1;                                                                                                                                                          
}

上面的这个AC代码是在刚才90分代码的基础上,增加了这一段玄学剪枝。

具体是什么意思呢?就是,我们在进行判断的时候,从右往左进行了判断。也就是说,给DEA赋值之后,从右往左判断了(D+E)%5=A是否成立。但是,这个玄学的剪枝还要加一个从左往右!

也就是说,就算这个式子右边是DEA,其中D=4、E=2、A=1。是不是没毛病,是不是要继续搜索下去。但是如果这个式子左边是DAE,即要判断(D+A)%5=E,从右往左计算时,要考虑进位,所以只要满足(D+A)%5=E(D+A+1)%5=E两者之一即可。但是你会发现,这两者都不满足!

也就是说,这个DEA的赋值情况,在右侧满足了条件,但是在左侧不满足,那这个搜索也不需要进行下去,因为它迟早会进行到左边,到达那个不满足的点!

对于这道题,没啥多说的,活到老学到老以及无奈的微笑:)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351