「ROI 2018 Day 2」无进位加法

题目

点这里看题目。

题目大意:给定一个序列 \(\{a_1,a_2,\dots,a_n\}\),构造一个 \(\{b_1,b_2,\dots,b_n\}\) 满足如下条件:

  • \(\{b_n\}\) 的「代数和」恰好等于其「按位或和」的结果。
  • 对于任意的 \(1\le k\le n\),有 \(b_k\ge a_k\)

并最小化 \(\sum b\),输出它。所有 \(a_i\) 都按照二进制输入,你的 \(\sum b\) 也应该按照二进制输出。

对于 \(100\%\) 的数据,满足 \(1\le n\le 3\times 10^5\)。此外,所有 \(a_i\) 的二进制表示的长度均不超过 \(3\times 10^5\),它们的长度之和也不超过 \(3\times 10^5\)

分析

从最简单的部分入手:怎么写暴力?我们尝试先解决 \(\sum b\) 的位数问题。也许我们可以进行二分,得到一个二分值 \(l_0\),并假设 \(0,1,2,\dots,l_0-1\) 这些二进制位上都是 1。根据题意,我们相当于是要将这些二进制位分配到 \(b\) 序列上,每位只能用一次,并且要满足 \(\forall 1\le k\le n,b_k\ge a_k\)

考虑检查这个 \(l_0\) 的可行性。不妨先找出可用的最高位 \(q\),我们先来用掉它。现在,最难处理的还是 \(a_{\max}\),设它的最高位为 \(t\)。讨论二者的关系:

  • 如果 \(q<t\),那么 GG 了,当前的二进制位不可能构造出一个 \(\ge a_{\max}\)\(b\)

  • 如果 \(q=t\),那么为了避免 GG,我们只能将 \(q\) 这一位用在 \(a_{\max}\) 对应的 \(b\) 上。

  • 如果 \(q>t\),那么实际上 \(q\) 放在任意一个 \(b\) 上,并且效果都是直接让那个 \(b\) 大于对应的 \(a\)。贪心地考虑,我们仍然选择让 \(q\) “吃掉” \(a_{\max}\)

注意到,考虑完 \(q\) 之后,我们得到了一个子问题,并且完全没有后效性,所以这个贪心是线性的。尝试模拟这个过程,我们只需要快速地找到 \(a_{\max}\)。注意到,按照这个算法所有可能出现的 \(a\),应当是原先 \(\{a_n\}\) 中每个元素以 1 开头的后缀构成的集合中的元素,这是相当有限的。因此,我们可以将它们拉出来排序,得到紧凑的排名。具体排序可以按照最高位依次排序,此处不再赘述。

考虑完了长度,我们再来寻找答案。我们只需要贪心地从高到低,检查每个位置能否填 0 即可。事实上,此时我们面对的问题和上述问题很类似,只不过并不是 \(0,1,2,\dots,l_0-1\) 这样连续的二进制位都可以用。稍微改动一下,仅仅找出可用的最高位 \(q\) 来判断即可。

如果 \(s\)\(a\) 的二进制长度之和,\(m\)\(a\) 的二进制长度最大值,这样做最好为 \(O(s+(m+n)^2)\),不够优秀。


我们尝试加速整个检查过程。一个简单的想法是,将模拟过程跳过,维护一些具有判断功能的数值

注意:虽然名义上我们在判断,但是现在请暂时把上面的“贪心+检查”的主体思路忘掉,下面我们几乎是在直接构造 \(\sum b\)

在判断长度的过程中,如果某一次可用的最高位是 \(q\),如何尽快地得知有没有可能构造出 \(\{b_n\}\)

\(\{a_n\}\) 不升地排序,并设 \(a_k\) 最高位为 \(t_k\)。结合一个 \(a_i=2^{k_i}\) 的子任务,我们可以得到启发:

  • \(0,1,2,\dots,q-1\) 位都可用的情况下,能够构造出 \(\{b_n\}\)必要条件为 \(\forall1\le k\le n,q-k+1\ge t_k\)

    说明:当 \(a_k\) 作为最大值时,我们至少已经用掉了 \(k-1\) 个二进制位。而此时我们还必须保证 \(a_k\) 对应的二进制位 \(\ge t_k\),因而得到这个条件;

  • \(0,1,2,\dots,q-1\) 位都可用的情况下,能够构造出 \(\{b_n\}\)充分条件为 \(\forall1\le k\le n,q-k+1>t_k\)

    说明:考虑我们的贪心过程,对于 \(a_1\),我们知道 \(q>t_1\),因此 \(a_1\) 会直接消失;此后,\(a_2\) 面对的是 \(q-1>t_2\),也会直接消失......因此,我们可以归纳说明,\(a_k\) 面对的最高位就是 \(q-k+1\),而这种情况下 \(a_k\) 就会直接消失。

这是一个相当紧的界!😄 根据第一条,我们可以知道当前情况下最高位至少要填到 \(q_{\max}=\max\{t_k+k-1\}\);而根据第二条,如果最高位为 \(q_{\max}\) 不行,那填到 \(q_{\max}+1\) 就一定可行!因此,我们实际上只需要检查 \(q_{\max}\) 作为填 1 的最高位究竟可不可行。

这种情况下,我们先选出满足 \(t_k+k-1\) 最大的最小的 \(k\)。这样的话,如果我们尝试在 \(q_{\max}\) 开始填 1,则对于 \(a_1,a_2,\dots,a_{k-1}\),它们遇到的就是第二条的情况。而对于 \(a_k\),它遇到的就是第一条的情况,也就是 \(q_{\max}-k+1=t_k\)。此时,我们只能抵消掉 \(a_k\) 的最高位,然后进入一个类似的子问题。如果子问题告诉我们可以构造,那么填在 \(q_{\max}\) 就没问题;否则,我们只能考虑填在 \(q_{\max}+1\) 的情况。在后面的情况中,从 \(\sum b\) 的角度来看,改变的只有第 \(q_{\max}+1\) 位和 \(q_{\max}-k+1\) 位,中间的那些位实际上都没有变。

需要注意的是,我们在同时进行判断和构造——如果能够构造,就构造出来并判断为“可构造”;反之就只能判断为“不可构造”。此外,我们还需要考虑先前填好的位的影响。由于 \(t_k\) 一定小于先前填好的位(先前填好的位一定是由 \(q_{\max}\) 生成的区间),所以我们得保证当前的 \(q_{\max}\) 不能高于已经填好的最后一位——换言之,出现了这种情况,构造就失败了。此外,如果我们发现 \(q_{\max}\) 作为最高位是不合法的,则我们还需要调整到 \(q_{\max}+1\) 位,而这时还得检查“不高于最后一位”的条件——也就是说,如果运气特别不好,我们递归后发现 \(q_{\max}\) 不合法,而 \(q_{\max}+1\) 填不了,就只能回溯

考虑维护这个过程中的信息:实际上我们只需要快速地找出 \(q_{\max}\)。注意到,一开始我们就可以得到所有有效的 \(a\) 排好序后的结果,因此可以在那个序列上建立线段树,维护最值。只不过并非所有序列中的元素都可以被选作最大值而已,稍微用一个极大值处理即可。

研究一下复杂度:如果不存在“递归发现 \(q_{\max}\) 不合法,且 \(q_{\max}+1\) 填不了”地情况,这个过程中每个元素只会被删减至多各两次,复杂度为 \(O(n\log(\sum L))\);而如果运气不好,存在上述情况,则由于每个被回溯的元素,都恰好对应了 \(q_{\max}\) 构造中所填入的 1,且 \(q_{\max}\) 的大小受到先前层的限制,一定是 \(O(L)\) 级别的。如果从 \(a_p\) 开始,从空串开始递归检查,则最多回溯 \(O(L_p)\) 个元素,而每个 \(a_p\) 最多有一次机会从空串开始递归,因此复杂度为 \(O((n+\sum L)\log(\sum L))\)

小结:

  1. 最基础的贪心:考虑最高位 \(q\) 来源于 \(b \ge a\) 的背景,我们通常会从高到低来比大小;而考虑 \(a_{\max}\) 是因为它现在是最紧而最亟需解决的一个值。从已有的背景中来寻找线索,从而为之后的思路提供方向

  2. 优化步骤中:尝试通过数值来判断或者限定可行性,这是一个很通用的思路。而通常来说我们倾向于寻找必要/充分条件。更进一步地,如果我们挖掘得够深,我们也可以直接透过数值生成有效的构造限制,而不是来判断。虽然是这么说,不过这个多半还是要积累感觉。

  3. 对于 \(a\) 的后缀按照最高位“分层”的排序方法还是比较有借鉴价值的。

  4. 最后这个复杂度分析非常神奇......

代码

#include <set>
#include <cstdio>
#include <vector>
#include <cstring>
#include <utility>
#include <algorithm>

#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )

const int INF = 1e9;
const int MAXN = 6e5 + 5;

template<typename _T>
_T Max( const _T a, const _T b ) {
    return a > b ? a : b;
}

typedef std :: pair<int, int> Node;
typedef std :: pair<int, int> Suffix;

std :: set<int> alive;

Node mx[MAXN << 2];
int tag[MAXN << 2];

int ini[MAXN];
Suffix all[MAXN];

std :: vector<int> vec[MAXN], rnk[MAXN];
std :: vector<Suffix> each[MAXN];

bool ans[MAXN];

int low[MAXN], seq[MAXN];

char buf[MAXN];

int lrg = 0;
int N, M = 0, tot = 0;

inline void Upt( const int &x ) {
    mx[x] = Max( mx[x << 1], mx[x << 1 | 1] );
}

inline void Add( const int &x, const int &delt ) {
    mx[x].first += delt, tag[x] += delt;
}

inline void Normalize( const int &x ) {
    if( ! tag[x] ) return ;
    Add( x << 1, tag[x] );
    Add( x << 1 | 1, tag[x] );
    tag[x] = 0;
}

void Build( const int x, const int l, const int r ) {
    if( l > r ) return ;
    if( l == r ) {
        mx[x] = { vec[all[l].first][all[l].second] + ini[l] - 1, l };
        if( all[l].second == ( int ) vec[all[l].first].size() - 1 ) alive.insert( l );
        else mx[x].first -= INF;
        return ;
    }
    int mid = ( l + r ) >> 1;
    Build( x << 1, l, mid );
    Build( x << 1 | 1, mid + 1, r );
    Upt( x );
}

void Update( const int x, const int l, const int r, const int segL, const int segR, const int delt ) {
    if( segL > segR ) return ;
    if( segL <= l && r <= segR ) { Add( x, delt ); return ; }
    int mid = ( l + r ) >> 1; Normalize( x );
    if( segL <= mid ) Update( x << 1, l, mid, segL, segR, delt );
    if( mid  < segR ) Update( x << 1 | 1, mid + 1, r, segL, segR, delt );
    Upt( x );
}

void Add( const int x, const int l, const int r, const int p, const int delt ) {
    if( l == r ) { mx[x].first += delt; return ; }
    int mid = ( l + r ) >> 1; Normalize( x );
    if( p <= mid ) Add( x << 1, l, mid, p, delt );
    else Add( x << 1 | 1, mid + 1, r, p, delt );
    Upt( x );
}

bool DFS( const int lim ) {
    if( mx[1].first < 0 ) return true;
    std :: vector<int> tmp; tmp.clear();
    int B = mx[1].first, u = mx[1].second, cnt = 0;
    if( B >= lim ) return false;
    while( * alive.rbegin() > u ) {
        cnt ++;
        int cur = *alive.rbegin();
        Update( 1, 1, tot, 1, cur, -1 );
        Add( 1, 1, tot, cur, - INF );
        alive.erase( cur ), tmp.push_back( cur );
    }
    // 先假设在 B+1 位上不用填
    rep( i, 0, cnt - 1 ) ans[B - i] = true;
    Update( 1, 1, tot, 1, u, -1 );
    Add( 1, 1, tot, u, - INF );
    alive.erase( u );
    int p = all[u].first, q = all[u].second;
    if( q > 0 ) {
        int v = rnk[p][q - 1];
        Update( 1, 1, tot, 1, v, +1 );
        Add( 1, 1, tot, v, INF );
        alive.insert( v );
    }
    lrg = Max( lrg, B );
    if( DFS( B - cnt ) ) 
        return ans[B - cnt] = true;
    if( q > 0 ) {
        int v = rnk[p][q - 1];
        Update( 1, 1, tot, 1, v, -1 );
        Add( 1, 1, tot, v, - INF );
        alive.erase( v );
    }
    if( B + 1 >= lim ) {
        rep( i, 0, cnt - 1 ) ans[i] = false;
        while( ! tmp.empty() ) {
            int cur = tmp.back(); tmp.pop_back();
            Update( 1, 1, tot, 1, cur, +1 );
            Add( 1, 1, tot, cur, + INF );
            alive.insert( cur );
        }
        Update( 1, 1, tot, 1, u, +1 );
        Add( 1, 1, tot, u, INF );
        alive.insert( u );
        return false;
    }
    ans[B + 1] = true, lrg = Max( lrg, B + 1 );
    return DFS( B - cnt + 1 ) && B + 1 < lim;
}

int main() {
    scanf( "%d", &N );
    rep( i, 1, N ) {
        scanf( "%s", buf );
        int len = strlen( buf );
        for( int j = 0 ; j < len ; j ++ ) {
            if( buf[j] == '0' ) continue;
            vec[i].push_back( len - j - 1 );
        }
        int n = vec[i].size();
        rnk[i].resize( vec[i].size() );
        std :: reverse( vec[i].begin(), vec[i].end() );
        for( int j = 0 ; j < n ; j ++ ) 
            each[vec[i][j]].push_back( { i, j } );
        M = Max( M, len );
    }
    rep( i, 0, M - 1 ) {
        int n = each[i].size();
        for( int j = 0 ; j < n ; j ++ )
            seq[j] = j, low[j] = each[i][j].second ? rnk[each[i][j].first][each[i][j].second - 1] : 0;
        std :: sort( seq, seq + n,
            [i] ( const int &a, const int &b ) -> bool {
                return low[a] == low[b] ? each[i][a].first < each[i][b].first : low[a] < low[b];
            } );
        for( int j = 0 ; j < n ; j ++ )
            all[rnk[each[i][seq[j]].first][each[i][seq[j]].second] = ++ tot] = each[i][seq[j]];
    }
    per( i, tot, 1 ) ini[i] = ini[i + 1] + ( all[i].second == ( int ) vec[all[i].first].size() - 1 );
    Build( 1, 1, tot );
    DFS( 1e9 );
    per( i, lrg, 0 ) putchar( '0' + ans[i] );
    puts( "" );
    return 0;
}
posted @ 2022-03-29 22:50  crashed  阅读(102)  评论(0编辑  收藏  举报