搜索技巧之折半搜索

前言

搜索本身就是一种布鲁特佛斯(Brute-Force)算法,所以它的优化是十分重要的,其中一种就是折半搜索。


正文

折半搜索的主要思想是:对于一类搜索问题,将本来直接搜索的算法转换成分别搜索两部分,再把两部分结果组合得到最终答案。通常这两部分大小相等,但有时候碍于空间或者时间限制,需要微调。

例题1

JZOJ 4841

JZOJ 4841

我们可以给每个人一个权值,这个权值可以是\{0,1,-1\},分别的意思是:
0:这个人不取出来。
1:这个人取出来分到1队。
-1:这个人取出来分到2队。
问题就变成了给每个人分配三个权值中的一个,和为0的方案数。
直接搜索复杂度是O(3^n),过不了。
使用折半搜索,对于前半部分搜索得出结果,设搜出来的和为s,使用map或者哈希在对应的位置存下结果,然后搜索后半部分,加上对应位置的结果。
但是这样会有重复算的情况,因为两种情况不同只取决于取出来的人而不是怎么分队。所以我们在搜索前半段时要用一个二进制数保存下所选人的情况,然后去重一下即可。
复杂度为前半部分+后半部分,也就是O(3^{\frac{n}{2}}+3^{\frac{n}{2}})=O(3^{\frac{n}{2}})
是可以在时限内通过的。

例题2

JZOJ 5797

JZOJ 5797

还是直接折半搜索,搜出前半部分后,我们需要记录下最后一个楼房的高度h1和楼房的金币之和s1
在后半部分搜索出来一个结果之后,我们需要记录跳的第一个楼房高度h2和金币之和s2,然后根据条件,必须满足:
h1≤h2
s1+s2≥k
也就是
s1≥k-s2
因此我们可以设f[i][j]表示前半部分的结果中满足h1≤is1≥j的情况有多少。由于数据很大没法直接存,因此需要离散化,离散化之后这个数组需要的空间是O(\frac{n}{2}2^{\frac{n}{2}}),由于出题人成心卡内存还是存不下,于是就需要把前半部分少搜一个,后半部分多搜一个,这样空间极限复杂度是O(19*2^{19}),时间极限复杂度是O(2^{21}),可以在时限内通过。
Code:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
using namespace std;

typedef long long ll;

int lenh, arrh[23], leng;
ll ans, k, g[43], gsum[(1 << 19) + 3];

int n, mid, h[43];
int buc[23][(1 << 19) + 3];

void dfs1(int now, ll sum, int las)
{
    if (now > mid) { gsum[++leng] = sum; return; }
    if (h[now] >= h[las]) dfs1(now + 1, sum + g[now], now);
    dfs1(now + 1, sum, las);
}

void dfs2(int now, ll sum, int las)
{
    if (now > mid)
    {
        buc[h[las]][lower_bound(gsum + 1, gsum + leng + 1, sum) - gsum]++;
        return;
    }
    if (h[now] >= h[las]) dfs2(now + 1, sum + g[now], now);
    dfs2(now + 1, sum, las);
}

void dfs3(int now, ll sum, int las, int fir)
{
    if (now > n)
    {
        if (fir) ans += buc[upper_bound(arrh + 1, arrh + lenh + 1, h[fir]) - arrh - 1][lower_bound(gsum + 1, gsum + leng + 1, k - sum) - gsum];
        else ans += buc[lenh][lower_bound(gsum + 1, gsum + leng + 1, k - sum) - gsum];
        return;
    }
    if (h[now] >= h[las])
    {
        if (!fir) dfs3(now + 1, sum + g[now], now, now);
        else dfs3(now + 1, sum + g[now], now, fir);
    }
    dfs3(now + 1, sum, las, fir);
}

int main()
{
    freopen("san.in", "r", stdin);
    freopen("san.out", "w", stdout);
    
    scanf("%d%lld", &n, &k);
    for (int i = 1; i <= n; i++) scanf("%d%lld", h + i, g + i);
    mid = n / 2 - 1; //前半部分少搜一个
    if (mid < 1) mid = 1;
    for (int i = 1; i <= mid; i++) arrh[++lenh] = h[i];
    sort(arrh + 1, arrh + lenh + 1);
    lenh = unique(arrh + 1, arrh + lenh + 1) - arrh - 1;
    for (int i = 1; i <= mid; i++) h[i] = lower_bound(arrh + 1, arrh + lenh + 1, h[i]) - arrh;
    dfs1(1, 0, 0);
    sort(gsum + 1, gsum + leng + 1);
    leng = unique(gsum + 1, gsum + leng + 1) - gsum - 1;
    dfs2(1, 0, 0);
    for (int i = 1; i <= lenh; i++)
        for (int j = leng; j >= 1; j--)
            buc[i][j] += buc[i - 1][j] + buc[i][j + 1] - buc[i - 1][j + 1]; //用类似二位前缀和的方法计算f
    dfs3(mid + 1, 0, 0, 0);
    printf("%lld\n", ans);

    fclose(stdin);
    fclose(stdout);
    return 0;
}

总结

很多看似DP,或者看上去很劝退的题目,其实要仔细观察数据范围,万一真的是暴力呢?如果一般暴力不行就优化,用各种方法优化,常数和复杂度都优化,这应该就是搜索题的应付方法了。

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

推荐阅读更多精彩内容