题解/算法 {2858. 病毒检测}

题解/算法 {2858. 病毒检测}

@LINK: https://www.acwing.com/problem/content/description/2861/;

没找到思路还是很难的…
其实没找到思路时 就去想暴力! 也就是, 这个模式串T 我们就暴力的枚举他 对于? 他有4种可能, 对于* 我们把他转换成若干个? (即?...?), 此时当然方案数是非常多的;
但是, 因为一共有500个长度为500的字符串 其实并不多, 我们把他构造成Trie树, 也就是 我们一边枚举T的各种形态 同时还要匹配Trie树 (也就是 我们枚举?, *的选择 他是必须要符合Trie树的 即是Trie树里的前缀); 这样 他的规模并不大, 因为Trie树 分支只有4个 且只有500个字符串 深度为500;

但是, 以下代码会超时的:

auto Dfs = [&]( auto _dfs, int _ind, ___TrieTree_::Node_ * _cur)->void{
    ASSERTcustom_( _cur != nullptr);

    auto id = reinterpret_cast<long long>( _cur) * 1003 + _ind;
    if( Set.find( id) != Set.end()){ return;}
    Set.insert( id);

    if( _ind >= N){
        if( _cur->Vis == 0){ // `cur`会多次访问, 比如`cur=CTC` `T=?*?`, 此时`T=[CT C ()], [() C TC]...`都是可以的;
            ANS += _cur->WholeCount;
        }
        _cur->Vis = 1;

        return;
    }

    if( T[_ind] == '*'){
        _dfs( _dfs, _ind+1, _cur); // skip

        FOR_( id, 0, Tr.BRANCHcount-1){
            if( Tr.NODEisEXIST( _cur->SonPtr[id])){
                _dfs( _dfs, _ind, _cur->SonPtr[id]);
            }
        }
    }
    else if( T[_ind] == '?'){
        FOR_( id, 0, Tr.BRANCHcount-1){
            if( Tr.NODEisEXIST( _cur->SonPtr[id])){
                _dfs( _dfs, _ind+1, _cur->SonPtr[id]);
            }
        }
    }
    else{
        auto nex = _cur->SonPtr[ Tr.GETbranchID( T[_ind])];
        if( Tr.NODEisEXIST( nex)){
            _dfs( _dfs, _ind+1, nex);
        }
    }
};
Dfs( Dfs, 0, Tr.RootNode);

cout<< M - ANS;

关键是如何优化超时呢?

先看一个错误的处理: 超时, 主要是由于* (因为他的可能性太多了), 于是 在if( T[_ind] == '*'){这里 添加一个if( _cur->Vis == 1){ continue;} _cur->Vis = 1;};的优化; 你可能认为, 比如当前是* cur = "XXX" 那么此时你的* 其实会遍历 Trie里所有以XXX的前缀, 因此 再遇到cur就不必遍历了;
. 说白了, 对于Dfs( ind, cur), 你只是对cur进行了记忆化DFS;
其实这样是错误的, 因为还有一个ind呢, 比如cur = "XXX", 而对于不同的ind 其实他产生的效果 是不一样的, 他对应的ind 可能是*, *?*, 此时虽然cur="XXX"即对应Trie的同一个节点 但是* 和 *?*他俩的后缀 和匹配cur的后缀, 这就不一样了 因为*的后缀 和 *?*的后缀是不同的;

因此, 还是要中规中矩, 既然是DFS, 那就对他整个函数参数进行记忆化 即(int, void*); 关键是怎么哈希呀? 把void*转换为int64? 然后用set< pair<int,int64> >? (这会超时的);
. 节点个数 经测试 只有2e5个, 即我们要对(500, 2e5)进行记忆化; 虽然说char [1e8]是会爆空间的, 但你可以用bitset<1e8> 她不会爆空间;
. . 虽然我们用的是指针节点(不是数组下标), 即现在的问题是 如何把void*转换为2e5呢? (千万别改底层代码 就太小题大做了), 你直接在每个节点里 放一个int id记录他的ID号不就可以啦;

static bitset<200005 * 502> Vis;
auto Dfs = [&]( auto _dfs, int _ind, ___TrieTree_::Node_ * _cur)->void{
    int id = _cur->Id * 502 + _ind; 
    if( Vis[ id]){ return;}
    Vis[ id] = 1;
	
    ...
};
  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值