problem
给定 nnn 个在 [0,2ω−1][0,2^\omega-1][0,2ω−1] 内的整数。执行下面操作两种操作共 n−1n-1n−1 次:
- 选择两个整数 x,yx,yx,y 从数列中删去,并加入 ⌊x∣y2⌋\lfloor\frac{x|y}{2}\rfloor⌊2x∣y⌋,这里的
|
表示按位或。 - 选择一个整数 xxx 从序列中删去。
不难发现每次操作后拥有的整数数量恰好少一,在 n−1n-1n−1 次操作后你将得到恰好一个整数。
最小化这个整数并输出你的结果。
多组数据,1≤T≤101\le T\le 101≤T≤10。
测试点编号 | n | w | 特殊性质 |
---|---|---|---|
$1 ∼ 2 $ | ≤6≤ 6≤6 | ≤60≤ 60≤60 | 无 |
$3 ∼ 5 $ | ≤8≤ 8≤8 | ≤60\le 60≤60 | 无 |
6∼86 ∼ 86∼8 | ≤300≤ 300≤300 | ≤12≤ 12≤12 | 无 |
9∼129 ∼ 129∼12 | ≤5000≤ 5000≤5000 | ≤18≤ 18≤18 | 无 |
13∼1513 ∼ 1513∼15 | ≤5000\le 5000≤5000 | ≤40≤ 40≤40 | 无 |
16∼1716 ∼ 1716∼17 | ≤105≤10^5≤105 | ≤60≤ 60≤60 | A |
18∼2018 ∼ 2018∼20 | ≤105\le 10^5≤105 | ≤60\le 60≤60 | 无 |
特殊限制 A:保证所有 aia_iai 都可以表示成 222 的整数次幂减一的形式。
solution
⌊x∣y2⌋\lfloor\frac{x|y}{2}\rfloor⌊2x∣y⌋ 的 /2/2/2 可以看作二进制右移一位,⌊x∣y2⌋⇔(x∣y)>>1⇔(x>>1)∣(y>>1)⇔⌊x2⌋∣⌊y2⌋\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⌊2x∣y⌋⇔(x∣y)>>1⇔(x>>1)∣∣(y>>1)⇔⌊2x⌋∣∣⌊2y⌋。
可以看到按位或操作后再右移等价于各个元素分别右移后再按位或。
设 ci:aic_i:a_ici:ai 参与合并操作的次数 / 右移的位数。
则 ans=⌊a12c1⌋∣⌊a22c2⌋∣...∣⌊an2cn⌋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}}\rfloorans=⌊2c1a1⌋∣∣∣⌊2c2a2⌋∣∣∣...∣∣∣⌊2cnan⌋。
有个显然是对的但又没想到的结论:在仅考虑合并操作的情况下,一组 {ci}\{c_i\}{ci} 合法当且仅当 ∑i=1n12ci=1\sum_{i=1}^{n}\frac{1}{2^{c_i}}=1∑i=1n2ci1=1。
加上考虑删除操作的情况,可知一组 {ci}\{c_i\}{ci} 合法当且仅当 ∑i=1n12ci≥1\sum_{i=1}^n\frac{1}{2^{c_i}}\ge 1∑i=1n2ci1≥1。
因为如果满足和 ≥1\ge 1≥1,那么一定存在子集 S⊂{1,2,...,n}S\subset\{1,2,...,n\}S⊂{1,2,...,n} 满足 ∑i∈S12ci=1\sum_{i\in S}\frac{1}{2^{c_i}}=1∑i∈S2ci1=1,那么不在 SSS 内的就是被删除元素。
换言之,∑i=1n12ci\sum_{i=1}^n\frac{1}{2^{c_i}}∑i=1n2ci1 越大越有可能进入备选答案集合。
case 6~8
基于此结论,我们可以设计一个非常暴力的状态转移方程。
设 fi,j:f_{i,j}:fi,j: 考虑 a[1∼i]a[1\sim i]a[1∼i] 这些数经过合并和删除一系列操作后的按位或为 jjj 的 ∑k=1i12ck\sum_{k=1}^i\frac{1}{2^{c_k}}∑k=1i2ck1 的最大值。
直接暴力转移,枚举 aia_iai 右移的位数 cic_ici。
fi,j∣(ai>>ci)←maxfi−1,jf_{i,j|(a_i>>c_i)}\leftarrow^{\max}f_{i-1,j} fi,j∣(ai>>ci)←maxfi−1,j
状态数 O(n2ω)O(n2^\omega)O(n2ω),转移 O(ω)O(\omega)O(ω),时间复杂度 O(Tn2ωω)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
考虑对 dpdpdp 进行优化。
考虑答案的上下界。显然答案不会超过 2ω−⌊log2n⌋2^{\omega-\lfloor\log_2n\rfloor}2ω−⌊log2n⌋(最大值与其余的数都进行合并操作)。
状态数 O(n2ω−⌊log2n⌋)=O(2ω)O(n2^{\omega-\lfloor\log_2n\rfloor})=O(2^\omega)O(n2ω−⌊log2n⌋)=O(2ω),转移 O(ω−⌊log2n⌋)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
通过最原始的暴力 dpdpdp 可知,我们能在 O(nω)O(n\omega)O(nω) 的时间内判断一个数 xxx 是否符合成为最后答案的要求。
即 O(n)O(n)O(n) 枚举 aia_iai,O(ω)O(\omega)O(ω) 枚举 cic_ici,在满足 (ai>>ci)∣x=x(a_i>>c_i)\big|x=x(ai>>ci)∣∣x=x 的前提下尽可能减小 cic_ici,等价于尽可能增大 12ci\frac{1}{2^{c_i}}2ci1。
通过 ∑i=1n12ci≥1?\sum_{i=1}^n\frac{1}{2^{c_i}}\ge 1?∑i=1n2ci1≥1? 来判断 xxx 能否成为候选答案。
所以我们贪心地从高位到低位考虑尽量填 000。
具体而言,先初始 ans=2ω−1ans=2^\omega-1ans=2ω−1(全 111),然后 i=ω→1i=\omega\rightarrow 1i=ω→1 顺次考虑 ans−2ians-2^ians−2i 是否可行,可行就 ans−=2ians-=2^ians−=2i 即可。
时间复杂度 O(Tnω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
,特殊情况 AAA。
显然只需要尽可能让最高位 111 的二进制位最低。
那么每次从序列中选两个最大值,如果相同就合并右移一位,否则直接扔掉最大值。
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
已经非常接近正解了。
事实上,从高位到低位按位考虑的时候,每个 iii 的 cic_ici 并不需要从头开始枚举。
因为为了满足前面更高位的一些限制的时候,有的 iii 就已经要求右移一定位数了。
那么此时我们完全可以直接从之前右移的位数继续累加考虑。
这样每个 iii 的 cic_ici 就只变化了一个 1∼ω1\sim \omega1∼ω 的范围。
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;
}