题目
点这里看题目。
分析
很有意思的题目!!!
简单分析可以发现 a
等价于二进制中有偶数个 1 , b
等价于二进制中有奇数个 1 。
这个部分可以直接考虑自顶向下的确定某一位为
a
或b
的过程。
设 (n) 为给定的 " 字符串 " 的长度,而 (N) 为输入的数,下面可以考虑 60 pts 怎么做:
当然可以直接数位 DP ,状态设计为 (f_{i,j,0/1,0/1}) 表示高 (i) 位,在 (x^j) 上面的贡献;其中第一组 0/1 表示是否有前缀相等,第二组 0/1 表示 1 的奇偶性。
转移可以考虑下一位放什么,具体细节就略过了,因为写题解的人自己都不会这个方法。
众所周知数位 DP 还有另外一种不用 DFS 的处理方法,就是枚举一段相等的前缀 + 钦定下一位更小 + 无限制贡献
。
那么我们只需要考虑怎么处理无限制,也就是 (0sim 2^k-1) 中任意选的贡献即可。由于一段前缀已知,需要加上,这里就叫它为 (p) 。
不妨将 (0sim 2^k-1) 的贡献表示为与 (p) 相关的多项式。那么可以设 (s_i) 为游戏局面的字符串,之后就可以定义:
并且还不难得到如下递推式:
注意到后面其实是多项式位移,那么可以暴力卷积做到 (O(k^2)) 地转移。
对于每一个前缀,再用 (O(k)) 的时间暴力计算,总时间就是 (O(nk^2)) 的。
常数很大,建议补题也不要轻易尝试。
接着考虑 80 pts 的做法。
这里会用到一个很奇妙的性质,你可以通过打表得到它:
对于 ([0,2^k-1]) 的数,设(S_e) 为二进制表示中有偶数个 1 的数的 (x^{k-1})之和, (S_o) 为二进制表示中有奇数个 1 的数的 (x^{k-1}) 之和,则 (S_e=S_o) 。
譬如,当 (k=3) 的时候,有 (0^2+3^2+5^2+6^2=1^2+2^2+4^2+7^2) 。
评论:太美妙了!
证明:
被降智了,直接证明不就完了。对于 (n) ,可以将 (n) 拆成若干个 (2^{e_1}+2^{e_2}+ldots+2^{e_p}) 的形式。为了区别,不妨设 (t_e=2^e) 。
根据多项式定理, (n^{k-1}) 必然为多个 " 齐次项 " 之和。考察其中的任意一项 (prod_{j=0}^{k-1} t_j^{e_j}) ,其中满足 (sum e_j=k-1) ,那么这一项的多项式系数是确定的,我们只需要考虑它的出现次数。显然只要某一个数包含了所有的非 0 次项,它就会被计算一次。因而可以设 (q=sum_j[e_j>0]) ,那么只需要在剩余的 (k-q) 位里面随便选都能算到这一项。我们只需要说明在剩余的 (k-q) 位里面,有奇数个 1 和有偶数个 1 的数个数相等即可。
新的命题可以直接对位数使用归纳法证明,这里不再赘述。因而这个性质得到了证明。
不难得到一个推广:
设 (C_{k,0}) 为 ([0,2^k-1]) 中二进制下有偶数个 1 的数的集合, (C_{k,1}) 为 ([0,2^k-1]) 中二进制下有奇数个 1 的数的集合;则下述命题成立:
[forall kin mathbb N_+,forall min mathbb N,m<k,sum_{pin C_{k,0}}p^m=sum_{qin C_{k,1}}q^m ]使用同样的方法即可证明。
接着知道了这个性质之后我们该怎么做?
之前我们在枚举一段前缀,枚举完之后如果有 (c) 个低位未定,我们就让低位在 ([0,2^c-1]) 里面随便乱选。不难发现上面的结论也可以推广到 ([p,p+2^c-1]) 的情况之下(二项式定理展开即证),因此我们只需要计算:
再除以 2 就好了鸭。
由于 (f) 是 (x) 的 (k-1) 次多项式,因此 (f) 的前缀和是 (x) 的 (k) 次多项式,那么就可以直接 Lagrange 插值得到我们要求的东西。
因此在 (c<k) 的时候需要暴力, (cge k) 的时候直接计算。
现在就得到了 (O(nk+k^3)) 的做法( x 值 " 连续 " 的 Lagrange 插值可优化)。
最后考虑 100pts 怎么做,这里的操作更加巧妙。
分析一下我们枚举前缀的过程,以 k=3,'n'= 100110101
为例:
c=4: 10010(....) <=> 10010(0000-1111)
c=5: 1000(.....) <=> 1000(00000-11111)
c=9: 0(........) <=> 0(00000000-11111111)
括号内就表示低位的范围。我们发现这个过程其实等价于是,保证前 (n-k) 位小于 (N) 的前 (n-k) 位的情况下,后面剩余的位随便乱选。
那么整体来看,怎么保证前 (n-k) 位小于 (N) 的前 (n-k) 位呢?直接提取出这 (n-k) 位的前缀、减去一再接上一个 11...1
的后缀就好了。比如这里的 100110101
就应该操作成 100101111
。
因此我们只需要做一次 Lagrange 插值,时间是 (O(n+k^3)) 。
亲测如果用多项式的方法需要开 -O2 。
小结:
- 巧妙之处一:发现和相等的性质,
这个完全只能靠运气和打表好嘛,然后将问题倒向了较简单的函数前缀和部分,直接用 Lagrange 插值。 - 巧妙之处二:对于多次 Lagrange 插值的优化,这样的构造思想可以借鉴。
代码
#pragma GCC opitmize( 2 )
#include <cstdio>
#include <cstring>
#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 -- )
const int mod = 1e9 + 7, inv2 = 5e8 + 4;
const int MAXN = 5e5 + 5, MAXK = 605;
template<typename _T>
void read( _T &x )
{
x = 0; char s = getchar(); int f = 1;
while( s < '0' || '9' < s ) { f = 1; if( s == '-' ) f = -1; s = getchar(); }
while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
x *= f;
}
template<typename _T>
void write( _T x )
{
if( x < 0 ) putchar( '-' ), x = - x;
if( 9 < x ) write( x / 10 );
putchar( x % 10 + '0' );
}
template<typename _T>
_T MAX( const _T a, const _T b )
{
return a > b ? a : b;
}
int y[MAXK];
int S[MAXK], T[MAXK], tmp[MAXK];
int fac[MAXN], ifac[MAXN], pw[MAXN];
char str[MAXN];
int num[MAXN];
int N, K;
inline int Qkpow( int, int );
inline int Mul( int x, int v ) { return 1ll * x * v % mod; }
inline int Inv( const int a ) { return Qkpow( a, mod - 2 ); }
inline int Sub( int x, int v ) { return ( x -= v ) < 0 ? x + mod : x; }
inline int Add( int x, int v ) { return ( x += v ) >= mod ? x - mod : x; }
inline int Qkpow( int base, int indx )
{
int ret = 1;
while( indx )
{
if( indx & 1 ) ret = Mul( ret, base );
base = Mul( base, base ), indx >>= 1;
}
return ret;
}
void Init( const int n )
{
fac[0] = 1; rep( i, 1, n ) fac[i] = Mul( fac[i - 1], i );
ifac[n] = Inv( fac[n] ); per( i, n - 1, 0 ) ifac[i] = Mul( ifac[i + 1], i + 1 );
pw[0] = 1;
for( int i = 1 ; i <= n ; i ++ )
pw[i] = Mul( pw[i - 1], 2 );
}
void Push( const int k )
{
int coe = 0, base = pw[k], now;
for( int i = 0 ; i < K ; i ++ ) tmp[i] = S[i];
for( int j = 0 ; j < K ; j ++ )
{
coe = 0, now = 1;
for( int i = j ; i < K ; i ++, now = Mul( now, base ) )
coe = Add( coe, Mul( Mul( T[i], fac[i] ), Mul( now, ifac[i - j] ) ) );
S[j] = Add( S[j], Mul( ifac[j], coe ) );
}
for( int j = 0 ; j < K ; j ++ )
{
coe = 0, now = 1;
for( int i = j ; i < K ; i ++, now = Mul( now, base ) )
coe = Add( coe, Mul( Mul( tmp[i], fac[i] ), Mul( now, ifac[i - j] ) ) );
T[j] = Add( T[j], Mul( ifac[j], coe ) );
}
}
int Ask( const int p, const int t )
{
int ret = 0, now = 1;
for( int i = 0 ; i < K ; i ++, now = Mul( now, p ) )
ret = Add( ret, Mul( now, t ? S[i] : T[i] ) );
return ret;
}
int Lagrange( const int x )
{
int ret = 0, tmp = 0;
rep( i, 0, K )
{
tmp = 1;
rep( j, 0, K )
if( i ^ j )
tmp = Mul( tmp, Mul( Sub( x, j ), Inv( Sub( i, j ) ) ) );
ret = Add( ret, Mul( tmp, y[i] ) );
}
return ret;
}
int main()
{
// freopen( "angry.in", "r", stdin );
// freopen( "angry.out", "w", stdout );
scanf( "%s", str );
N = strlen( str ), read( K );
std :: reverse( str, str + N );
for( int i = 0 ; i < K ; i ++ ) read( S[i] );
rep( i, 0, K )
{
y[i] = 0;
for( int j = 0, pw = 1 ; j < K ; j ++, pw = Mul( pw, i ) )
y[i] = Add( y[i], Mul( S[j], pw ) );
if( i ) y[i] = Add( y[i], y[i - 1] );
}
Init( MAX( N, K ) );
int above = 0, ans = 0, typ = 0;
for( int i = 0 ; i < N ; i ++ )
above = Add( above, Mul( pw[i], str[i] == '1' ) ), typ ^= str[i] == '1';
for( int i = 0 ; i < K ; i ++ )
{
typ ^= str[i] == '1';
above = Sub( above, Mul( pw[i], str[i] == '1' ) );
if( str[i] == '1' ) ans = Add( ans, Ask( above, typ ) );
if( i < N - 1 ) Push( i );
}
write( Add( ans, Mul( Lagrange( Sub( above, 1 ) ), inv2 ) ) ), putchar( '
' );
return 0;
}