「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;
}
posted @ 2022-11-13 18:07  crashed  阅读(54)  评论(0编辑  收藏  举报