zoukankan      html  css  js  c++  java
  • [NOI Online 2021 提高组] 愤怒的小N

    题目

    点这里看题目。

    分析

    很有意思的题目!!!

    简单分析可以发现 a 等价于二进制中有偶数个 1 , b 等价于二进制中有奇数个 1 。

    这个部分可以直接考虑自顶向下的确定某一位为 ab 的过程。

    (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) 为游戏局面的字符串,之后就可以定义:

    [S_k(p)=sum_{j=0}^{2^{k}-1}[s_j='a']f(p+j)\ T_k(p)=sum_{j=0}^{2^{k}-1}[s_j='b']f(p+j) ]

    并且还不难得到如下递推式:

    [S_k(p)=S_{k-1}(p)+T_{k-1}(p+2^{k-1})\ T_k(p)=T_{k-1}(p)+S_{k-1}(p+2^{k-1}) ]

    注意到后面其实是多项式位移,那么可以暴力卷积做到 (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]) 的情况之下(二项式定理展开即证),因此我们只需要计算:

    [sum_{j=0}^{2^c-1}f(p+j)=sum_{j=p}^{p+2^c-1}f(j) ]

    再除以 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 。

    小结:

    1. 巧妙之处一:发现和相等的性质这个完全只能靠运气和打表好嘛,然后将问题倒向了较简单的函数前缀和部分,直接用 Lagrange 插值。
    2. 巧妙之处二:对于多次 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;
    }
    
  • 相关阅读:
    codeforces 713A A. Sonya and Queries(状态压缩)
    2016大连网赛
    hdu-5834 Magic boy Bi Luo with his excited tree(树形dp)
    codeforces gym-101078
    ifrog-1028 Bob and Alice are playing numbers(trie树)
    codeforces 477B B. Dreamoon and Sets(构造)
    codeforces 477A A. Dreamoon and Sums(数学)
    三角形划分区域
    当总统
    Friends number
  • 原文地址:https://www.cnblogs.com/crashed/p/14586229.html
Copyright © 2011-2022 走看看