「ARC086D」Shift and Decrement
题目
点这里看题目。
给定一个长度为 \(n\) 的正整数序列 \(\{A_i\}_{i=1}^n\),和正整数参数 \(k\)。
对于序列 \(A\),进行至多 \(k\) 轮操作,每轮操作为以下两种之一:
- Shift 操作:对于 \(1\le i\le n\),令 \(A_i\gets \lfloor\frac{A_i}{2}\rfloor\)。
- Decrement 操作:对于 \(1\le i\le n\),令 \(A_i\gets A_{i}-1\)。特别地,如果 \(A\) 中存在 \(0\),则不能执行该操作。
求最终得到的序列情况的数量,对 \(10^9+7\) 取模。
所有数据满足 \(1\le n\le 200,1\le A_i,k\le 10^{18}\)。
分析
唯一有用的观察就是:Decrement 操作不会改变差分数组。这也就是说,我们仅需要对于差分数组相同的情况去重。
Remark.
这样来说,一个很自然的思路不就是“找出所有不同的差分数组”吗?
即便想到了“差分”也没有往后想,最初思考的时候还是比较散,没有深挖。
再来看 Shift 操作:由于折半会导致 \(A\) 掉得很快,所以有效的 Shift 操作很少(很明显应该在 \(\log_2A_{\max}\) 级别)。在此,我们顺便发现答案和 \(A\) 的顺序无关,所以我们排个序,那么有效的 Shift 操作数量为 \(L=\lfloor\log_2 A_n\rfloor\)。
那么,当我们把 Shift 和 Decrement 加起来的时候,我们不得不面对复杂的操作情况。这里又有一个常见的切入点——因为操作是有限的,所以我们尽量节约操作次数。那么,很容易想到一点:一次 Shift 操作之前若有 \(2\) 次连续 Decrement 操作,则这 \(2\) 次可以被该 Shift 操作之后的 \(1\) 次 Decrement 等效替代 。如果原本有 \(x\) 次,则替代之后只有 \(\lceil\frac x 2\rceil\),肯定不会变劣。所以,一次 Shift 操作之前至多有一次 Decrement 操作。
从另一个角度来看,经过 \(k\) 次 Shift 之后,一次 Decrement 操作接近于减去 \(2^k\) 而非 \(1\)。这是对于“原始 \(A_i\)”来考虑得到的。这实质上是在调整操作顺序,那我们能不能把 Shift 之前的 Decrement 全部收拢?可以的!上面的观察就是调整操作顺序的逻辑,可以验证最后的结果一定相同。
小结一下,操作实际上可以用三个操作 \((P,s,Q)\) 来描述,含义为:
- 对于 \(1\le i\le n\),令 \(A_i\gets \lfloor\frac{A_i-P}{2^s}\rfloor-Q\)。
自然地有限制 \(0\le P<2^s\),否则我们可以把 \(P\) 的 \(2^s\) 次操作收拢放到 \(Q\) 里去。
Remark.
关键不仅在于想不想得到,还在于能不能把繁杂的观察串起来,变成一个较好的结果。缺就缺在“组合思路”这一步上!也许可以用纸笔辅助,把想到的重要结论全部罗列下来。
Note.
另一个切入口在于,我们先枚举 \(s\),然后形式化地写下来操作过程,再去发现一些结论。这样可能会更自然一点。
显然,\(s\) 是可枚举的。而 \(\lfloor\frac{A_i-P}{2^s}\rfloor\) 实际上只和 \(A_i\bmod 2^s\) 与 \(P\) 的大小关系相关,所以不考虑 \(Q\) 时,值得考虑的情况只有 \(O(n)\) 个。每个值得考虑的情况中,\(P\) 的取值为一段区间,而我们只需要考虑其中 \(\operatorname{popcount}(P)\) 最小的那一个(准确来说就是最小的 \(\operatorname{popcount}\))。容易发现区间 \([l,r]\) 的最小 \(\operatorname{popcount}\) 可以用一种类似于树状数组更新的过程 \(O(\log r)\) 地找出来,不再赘述。
容易发现,每种值得考虑的情况就变成了只有 Decrement 的情况。差分数组的情况的计算过程实际上就是计算区间的并的大小,排个序就能搞定。
最终复杂度为 \(O(Ln^2\log(Ln))\),如果用 hash 的话可以去掉 \(\log\)。
代码
#include <cstdio>
#include <vector>
#include <iostream>
#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 -- )
typedef long long LL;
const LL INF = 2e18;
const int inf = 1e9;
const int mod = 1e9 + 7;
const int MAXN = 205, MAXS = 20005;
template<typename _T>
inline void Read( _T &x ) {
x = 0; char s = getchar(); bool f = false;
while( ! ( '0' <= s && s <= '9' ) ) { f = s == '-', s = getchar(); }
while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
if( f ) x = -x;
}
template<typename _T>
inline void Write( _T x ) {
if( x < 0 ) putchar( '-' ), x = -x;
if( 9 < x ) Write( x / 10 );
putchar( x % 10 + '0' );
}
typedef std :: pair<LL, LL> Range;
typedef std :: vector<LL> Diffrnc;
typedef std :: pair<Diffrnc, Range> Projct;
Projct all[MAXS];
int cnt = 0;
LL A[MAXN], tmp[MAXN];
LL C[MAXN]; int tot = 0;
int N; LL K;
inline int Qkpow( int, int );
inline int Inv( const int &a ) { return Qkpow( a, mod - 2 ); }
inline int Mul( int x, const int &v ) { return 1ll * x * v % mod; }
inline int Sub( int x, const int &v ) { return ( x -= v ) < 0 ? x + mod : x; }
inline int Add( int x, const int &v ) { return ( x += v ) >= mod ? x - mod : x; }
inline int& MulEq( int &x, const int &v ) { return x = 1ll * x * v % mod; }
inline int& SubEq( int &x, const int &v ) { return ( x -= v ) < 0 ? ( x += mod ) : x; }
inline int& AddEq( int &x, const int &v ) { return ( x += v ) >= mod ? ( x -= mod ) : x; }
inline int Qkpow( int base, int indx ) {
int ret = 1;
while( indx ) {
if( indx & 1 ) MulEq( ret, base );
MulEq( base, base ), indx >>= 1;
}
return ret;
}
inline LL Lowbit( const LL &x ) {
return x & ( - x );
}
inline int FindMin( LL l, const LL &r ) {
if( ! l ) return 0;
int ret = inf;
for( ; l <= r ; l += Lowbit( l ) )
ret = std :: min( ret, ( int ) __builtin_popcountll( l ) );
return ret;
}
int main() {
Read( N ), Read( K );
rep( i, 1, N ) Read( A[i] );
std :: sort( A + 1, A + 1 + N );
int ans = 0;
if( K > 60 || ( 1ll << K ) > A[N] ) AddEq( ans, 1 );
for( int k = 0 ; ( 1ll << k ) <= A[N] && k <= K ; k ++ ) {
LL m = 1ll << k; tot = N;
rep( i, 1, N ) C[i] = A[i] & ( m - 1 );
C[++ tot] = -1, C[++ tot] = m - 1;
std :: sort( C + 1, C + 1 + tot );
rep( i, 1, tot - 1 ) if( C[i] < C[i + 1] ) {
int cst = FindMin( C[i] + 1, C[i + 1] ) + k;
if( cst > K ) { continue; } bool flg = true;
rep( j, 1, N ) {
tmp[j] = ( A[j] >> k ) - ( C[i + 1] > A[j] % m );
if( tmp[j] < 0 ) { flg = false; break; }
}
if( ! flg ) { break; } Diffrnc dif( N - 1, 0 );
rep( j, 2, N ) dif[j - 2] = tmp[j] - tmp[j - 1], flg &= dif[j - 2] == 0;
all[++ cnt] = std :: make_pair( dif, Range( std :: max( 0ll, tmp[1] - ( K - cst ) ), tmp[1] ) );
if( flg && all[cnt].second.first == 0 ) all[cnt].second.first ++;
if( all[cnt].second.first > all[cnt].second.second ) cnt --;
}
}
std :: sort( all + 1, all + cnt + 1 );
for( int l = 1, r ; l <= cnt ; l = r ) {
for( r = l + 1 ; r <= cnt && all[l].first == all[r].first ; r ++ );
LL lstL = all[l].second.first, lstR = all[l].second.second;
rep( k, l + 1, r - 1 ) {
if( all[k].second.first - 1 <= lstR )
lstR = std :: max( lstR, all[k].second.second );
else {
AddEq( ans, ( lstR - lstL + 1 ) % mod );
lstL = all[k].second.first, lstR = all[k].second.second;
}
}
AddEq( ans, ( lstR - lstL + 1 ) % mod );
}
Write( ans ), putchar( '\n' );
return 0;
}