P10218 [省選聯考 2024] 魔法手杖 題解

peiwenjun發表於2024-07-03

題目描述:

給定序列 \(a_1,\cdots,a_n\)\(b_1,\cdots,b_n\) ,滿足 \(a_i\in [0,2^k-1],b_i\ge 0\),你需要給出 \(S\subseteq \{1,\cdots,n\}\)\(x\in [0,2^k-1]\) 滿足:

  • \(\sum\limits_{i\in S}b_i\le m\)
  • 最大化 \(val(S,x)=\min\big(\min\limits_{i\in S}(a_i+x),\min\limits_{i\not\in S}(a_i\oplus x)\big)\)

\(val(S,x)\) 的最大值。


資料範圍:

  • \(1\le n\le 10^5,1\le\sum n\le 5\cdot 10^5\)
  • \(0\le m,b_i\le 10^9\)
  • \(0\le k\le 120\)

時間限制 \(\texttt{1.5s}\),空間限制 \(\texttt{1024MB}\)


分析:

特判掉 \(\sum_{i=1}^nb_i\le m\) 的情況,此時 \(ans=\min\limits_{1\le i\le n}a_i+2^k-1\)

看到異或直接往 \(\texttt{trie}\) 樹上想,考慮從高到低確定 \(x\)\(ans\) 的每一位。

\(\texttt{trie}\) 樹上遞迴時,我們已經考慮當前節點子樹外的所有狀態,並且正在考慮當前節點深度 \(d\) 的決策

如果 \(ans\)\(d\) 位取 \(1\) ,那麼左右子樹中至少有一棵為空或者全部放入 \(S\)

如果 \(ans\)\(d\) 位取 \(0\),那麼\(x\) 當前位取值相反的子樹一定取不到最小值,直接不予考慮。

遞迴時列舉 \(x\) 當前位取 \(0\) 還是 \(1\) 即可。

時間複雜度 \(\mathcal O(\sum nk)\)

#include<bits/stdc++.h>
#define ll __int128
using namespace std;
const int maxn=1e5+5,maxm=1.2e7+5;
int k,m,n,tot;
int ch[maxm][2];
ll res,a[maxn],b[maxn],pw[125],v1[maxm],v2[maxm];
ll read()
{
    ll q=0;char ch=getchar();
    while(!isdigit(ch)) ch=getchar();
    while(isdigit(ch)) q=10*q+ch-'0',ch=getchar();
    return q;
}
void write(ll x)
{
    if(x>=10) write(x/10);
    putchar(x%10+'0');
}
void dfs(int p,int d,ll amin,ll bsum,ll x,ll ans)
{
    if(!~d) return res=max(res,ans),void();
    if(!p) return res=max(res,amin+x+pw[d+1]-1),void();
    int flg=0,lc=ch[p][0],rc=ch[p][1];
    ///x=0,ans=1
    if(bsum+v2[lc]<=m&&min(amin,v1[lc])+x+pw[d]-1>=ans+pw[d])
        flg=1,dfs(rc,d-1,min(amin,v1[lc]),bsum+v2[lc],x,ans+pw[d]);
    ///x=1,ans=1
    if(bsum+v2[rc]<=m&&min(amin,v1[rc])+x+pw[d+1]-1>=ans+pw[d])
        flg=1,dfs(lc,d-1,min(amin,v1[rc]),bsum+v2[rc],x+pw[d],ans+pw[d]);
    if(flg) return ;
    ///x=0,ans=0
    dfs(lc,d-1,amin,bsum,x,ans);
    ///x=1,ans=0
    dfs(rc,d-1,amin,bsum,x+pw[d],ans);
}
int main()
{
    for(int i=0;i<125;i++) pw[i]=i?pw[i-1]<<1:1;
    v1[0]=pw[120];
    for(int c=read(),t=read();t--;)
    {
        n=read(),m=read(),k=read();
        for(int i=1;i<=n;i++) a[i]=read();
        for(int i=1;i<=n;i++) b[i]=read();
        memset(ch+1,0,8*tot),memset(v2+1,0,16*tot),v1[tot=1]=pw[k];
        for(int i=1;i<=n;i++)
            for(int j=k-1,p=1;;j--)
            {
                v1[p]=min(v1[p],a[i]),v2[p]+=b[i];
                if(!~j) break;
                int v=a[i]>>j&1;
                if(!ch[p][v]) v1[ch[p][v]=++tot]=pw[k];
                p=ch[p][v];
            }
        if(v2[1]<=m) res=v1[1]+pw[k]-1;
        else res=0,dfs(1,k-1,pw[k],0,0,res);
        write(res),putchar('\n');
    }
    return 0;
}

總結:

  • \(\texttt{trie}\) 樹有關的最最佳化問題一般會用到貪心的思想,筆者獨立思考的時候跟著特殊性質 \(\texttt{A,B}\) 走到 \(\texttt{dp}\) 的坑裡就爬不出來了。

相關文章