合成小丹(dp+二进制按位或+结论)

本文介绍了一种基于位操作的算法优化方法,通过分析特定操作的数学特性,利用位移和按位或运算来减少计算复杂度。针对不同规模的数据输入,提出了多种优化策略,包括暴力状态转移方程、上下界限定优化、贪心策略以及特殊情况下最优解的求解。
摘要由CSDN通过智能技术生成

problem

给定 n n n 个在 [ 0 , 2 ω − 1 ] [0,2^\omega-1] [0,2ω1] 内的整数。执行下面操作两种操作共 n − 1 n-1 n1 次:

  1. 选择两个整数 x , y x,y x,y 从数列中删去,并加入 ⌊ x ∣ y 2 ⌋ \lfloor\frac{x|y}{2}\rfloor 2xy,这里的 | 表示按位或。
  2. 选择一个整数 x x x 从序列中删去。

不难发现每次操作后拥有的整数数量恰好少一,在 n − 1 n-1 n1 次操作后你将得到恰好一个整数。

最小化这个整数并输出你的结果。

多组数据, 1 ≤ T ≤ 10 1\le T\le 10 1T10

测试点编号nw特殊性质
$1 ∼ 2 $ ≤ 6 ≤ 6 6 ≤ 60 ≤ 60 60
$3 ∼ 5 $ ≤ 8 ≤ 8 8 ≤ 60 \le 60 60
6 ∼ 8 6 ∼ 8 68 ≤ 300 ≤ 300 300 ≤ 12 ≤ 12 12
9 ∼ 12 9 ∼ 12 912 ≤ 5000 ≤ 5000 5000 ≤ 18 ≤ 18 18
13 ∼ 15 13 ∼ 15 1315 ≤ 5000 \le 5000 5000 ≤ 40 ≤ 40 40
16 ∼ 17 16 ∼ 17 1617 ≤ 1 0 5 ≤10^5 105 ≤ 60 ≤ 60 60A
18 ∼ 20 18 ∼ 20 1820 ≤ 1 0 5 \le 10^5 105 ≤ 60 \le 60 60

特殊限制 A:保证所有 a i a_i ai 都可以表示成 2 2 2 的整数次幂减一的形式。

solution

⌊ x ∣ y 2 ⌋ \lfloor\frac{x|y}{2}\rfloor 2xy / 2 /2 /2 可以看作二进制右移一位, ⌊ x ∣ y 2 ⌋ ⇔ ( x ∣ y ) > > 1 ⇔ ( x > > 1 ) ∣ ( y > > 1 ) ⇔ ⌊ x 2 ⌋ ∣ ⌊ y 2 ⌋ \lfloor\frac{x|y}{2}\rfloor\Leftrightarrow (x|y)>>1\Leftrightarrow (x>>1)\big|(y>>1)\Leftrightarrow \lfloor\frac{x}{2}\rfloor\big|\lfloor\frac{y}{2}\rfloor 2xy(xy)>>1(x>>1)(y>>1)2x2y

可以看到按位或操作后再右移等价于各个元素分别右移后再按位或。

c i : a i c_i:a_i ci:ai 参与合并操作的次数 / 右移的位数。

a n s = ⌊ a 1 2 c 1 ⌋ ∣ ⌊ a 2 2 c 2 ⌋ ∣ . . . ∣ ⌊ a n 2 c n ⌋ ans=\lfloor\frac{a_1}{2^{c_1}}\rfloor\Big|\lfloor\frac{a_2}{2^{c_2}}\rfloor\Big|...\Big|\lfloor\frac{a_n}{2^{c_n}}\rfloor ans=2c1a12c2a2...2cnan

有个显然是对的但又没想到的结论:在仅考虑合并操作的情况下,一组 { c i } \{c_i\} {ci} 合法当且仅当 ∑ i = 1 n 1 2 c i = 1 \sum_{i=1}^{n}\frac{1}{2^{c_i}}=1 i=1n2ci1=1

加上考虑删除操作的情况,可知一组 { c i } \{c_i\} {ci} 合法当且仅当 ∑ i = 1 n 1 2 c i ≥ 1 \sum_{i=1}^n\frac{1}{2^{c_i}}\ge 1 i=1n2ci11

因为如果满足和 ≥ 1 \ge 1 1,那么一定存在子集 S ⊂ { 1 , 2 , . . . , n } S\subset\{1,2,...,n\} S{1,2,...,n} 满足 ∑ i ∈ S 1 2 c i = 1 \sum_{i\in S}\frac{1}{2^{c_i}}=1 iS2ci1=1,那么不在 S S S 内的就是被删除元素。

换言之, ∑ i = 1 n 1 2 c i \sum_{i=1}^n\frac{1}{2^{c_i}} i=1n2ci1 越大越有可能进入备选答案集合

  • case 6~8

基于此结论,我们可以设计一个非常暴力的状态转移方程。

f i , j : f_{i,j}: fi,j: 考虑 a [ 1 ∼ i ] a[1\sim i] a[1i] 这些数经过合并和删除一系列操作后的按位或为 j j j ∑ k = 1 i 1 2 c k \sum_{k=1}^i\frac{1}{2^{c_k}} k=1i2ck1 的最大值。

直接暴力转移,枚举 a i a_i ai 右移的位数 c i c_i ci
f i , j ∣ ( a i > > c i ) ← max ⁡ f i − 1 , j f_{i,j|(a_i>>c_i)}\leftarrow^{\max}f_{i-1,j} fi,j(ai>>ci)maxfi1,j
状态数 O ( n 2 ω ) O(n2^\omega) O(n2ω),转移 O ( ω ) O(\omega) O(ω),时间复杂度 O ( T n 2 ω ω ) O(Tn2^\omega\omega) O(Tn2ωω)

for( int i = 0;i <= n;i ++ )
    for( int j = 0;j < (1 << w);j ++ )
        dp[i][j] = -1;
dp[0][0] = 0;
for( int i = 1;i <= n;i ++ ) 
    for( int j = 0;j <= (1 << w);j ++ ) {
        dp[i][j] = max( dp[i][j], dp[i - 1][j] );
        if( dp[i - 1][j] != -1 )
            for( int k = 0;k <= w;k ++ )
                dp[i][j | (a[i] >> k)] = max( dp[i][j | (a[i] >> k)], dp[i - 1][j] + 1.0 / (1 << k) );
    }
for( int i = 0;i < (1 << w);i ++ )
    if( dp[n][i] >= 1 ) { printf( "%lld\n", i ); break; }
  • case 9~12

考虑对 d p dp dp 进行优化。

考虑答案的上下界。显然答案不会超过 2 ω − ⌊ log ⁡ 2 n ⌋ 2^{\omega-\lfloor\log_2n\rfloor} 2ωlog2n(最大值与其余的数都进行合并操作)。

状态数 O ( n 2 ω − ⌊ log ⁡ 2 n ⌋ ) = O ( 2 ω ) O(n2^{\omega-\lfloor\log_2n\rfloor})=O(2^\omega) O(n2ωlog2n)=O(2ω),转移 O ( ω − ⌊ log ⁡ 2 n ⌋ ) O(\omega-\lfloor\log_2n\rfloor) O(ωlog2n),时间复杂度 O ( T ω 2 ω ) O(T\omega2^\omega) O(Tω2ω)

int m = 1 << (int)( w - log2( n ) + 1 );
for( int i = 0;i <= m;i ++ ) dp[0][i] = 0;
for( int i = 1;i <= n;i ++ ) {
    int d = i & 1;
    for( int j = 0;j <= m;j ++ ) dp[d][j] = dp[d ^ 1][j];
    for( int j = 0;j <= m;j ++ ) {
        for( int k = 0;k <= w;k ++ )
            if( ( j | (a[i] >> k) ) <= m )
                dp[d][j | (a[i] >> k)] = max( dp[d][j | (a[i] >> k)], dp[d ^ 1][j] + (1 << w - k) );
    }
}
for( int i = 0;i <= m;i ++ )
    if( dp[n & 1][i] >= (1 << w) ) { printf( "%lld\n", i ); break; }
  • case 13~15

通过最原始的暴力 d p dp dp 可知,我们能在 O ( n ω ) O(n\omega) O(nω) 的时间内判断一个数 x x x 是否符合成为最后答案的要求。

O ( n ) O(n) O(n) 枚举 a i a_i ai O ( ω ) O(\omega) O(ω) 枚举 c i c_i ci,在满足 ( a i > > c i ) ∣ x = x (a_i>>c_i)\big|x=x (ai>>ci)x=x 的前提下尽可能减小 c i c_i ci,等价于尽可能增大 1 2 c i \frac{1}{2^{c_i}} 2ci1

通过 ∑ i = 1 n 1 2 c i ≥ 1 ? \sum_{i=1}^n\frac{1}{2^{c_i}}\ge 1? i=1n2ci11? 来判断 x x x 能否成为候选答案。

所以我们贪心地从高位到低位考虑尽量填 0 0 0

具体而言,先初始 a n s = 2 ω − 1 ans=2^\omega-1 ans=2ω1(全 1 1 1),然后 i = ω → 1 i=\omega\rightarrow 1 i=ω1 顺次考虑 a n s − 2 i ans-2^i ans2i 是否可行,可行就 a n s − = 2 i ans-=2^i ans=2i 即可。

时间复杂度 O ( T n ω 2 ) O(Tn\omega^2) O(Tnω2)

bool check( int ans ) {
    int sum = 0;
    for( int i = 1;i <= n;i ++ ) {
        for( int j = 0;j <= w;j ++ )
            if( ( ans | (a[i] >> j) ) == ans ) { sum += ( 1ll << w - j ); break; }
        if( sum >= (1ll << w) ) return 1;
    }
    return 0;
}
int ans = ( 1ll << w ) - 1;
for( int i = w - 1;~ i;i -- )
    if( check( ans ^ (1ll << i) ) ) ans ^= (1ll << i);
printf( "%lld\n", ans );
  • case 16~17,特殊情况 A A A

显然只需要尽可能让最高位 1 1 1 的二进制位最低。

那么每次从序列中选两个最大值,如果相同就合并右移一位,否则直接扔掉最大值。

priority_queue < int > q;
for( int i = 1;i <= n;i ++ ) q.push( a[i] );
while( q.size() > 1 ) {
    int x = q.top(); q.pop();
    int y = q.top(); q.pop();
    if( x == y ) q.push( x >> 1 );
    else q.push( y );
}
printf( "%lld\n", q.top() );
  • case 1~20

其实 case 13~15 已经非常接近正解了。

事实上,从高位到低位按位考虑的时候,每个 i i i c i c_i ci 并不需要从头开始枚举。

因为为了满足前面更高位的一些限制的时候,有的 i i i 就已经要求右移一定位数了。

那么此时我们完全可以直接从之前右移的位数继续累加考虑。

这样每个 i i i c i c_i ci 就只变化了一个 1 ∼ ω 1\sim \omega 1ω 的范围。

code

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define maxn 100005
int T, n, w;
int a[maxn], temp[maxn], g[maxn];

signed main() {
    scanf( "%lld", &T );
    while( T -- ) {
        scanf( "%lld %lld", &n, &w );
        for( int i = 1;i <= n;i ++ ) scanf( "%lld", &a[i] );
        memset( g, 0, sizeof( g ) );
        int ans = (1ll << w) - 1;
        for( int k = w - 1;~ k;k -- ) {
            int now = ans ^ (1ll << k), sum = 0;
            for( int i = 1;i <= n;i ++ ) {
                temp[i] = g[i];
                while( ( a[i] >> g[i] | now ) ^ now ) g[i] ++;
                sum += 1ll << w - g[i];
                if( sum >= (1ll << w) ) sum = (1ll << w); //一直加可能炸long long
            }
            if( sum >= (1ll << w) ) ans = now;
            else memcpy( g, temp, sizeof( g ) );
        }
        printf( "%lld\n", ans );
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值