• [LOJ6055]「from CommonAnts」一道数学题 加强版


    题目

    点这里看题目。

    分析

    首先可以娴熟地推倒一发式子:

    [egin{split}&f_k(n)&overset{mathrm{def}}{=}f(k,n)\Rightarrow &f_k(n)&=sum_{i=1}^{n-1}f_k(i)+n^k\end{split} ]

    定义(S_k(n)=sum_{i=1}^{n}f_k(i)),做差计算可以得到:

    [egin{split}Rightarrow &&S_k(n)&&=2S_k(n-1)+n^k\&&&&=dots\&&&&=sum_{i=1}^n2^{n-i}i^k\&&&&=2^nsum_{i=1}^n2^{-i}i^k\&&&a&overset{mathrm {def}}{=}2^{-1}\Rightarrow &&S_k(n)&&=2^nsum_{i=1}^na^ii^kend{split} ]

    自然我们可以得到:

    [f_k(n)=n^k+2^{n-1}sum_{i=1}^{n-1}a^ii^k ]

    为了方便,我们就可以定义出:

    [F_k(n)=sum_{i=1}^{n-1}a^ii^k ]

    问题于是转化为了:如何快速求出(F_k(n))

    60 pts ~ 80 pts

    可以发现(F_k(n))就是一个前缀和,我们尝试错位相减:

    [egin{split}(1-a)F_k(n)&=sum_{i=1}^na^ii^k-sum_{i=1}^{n+1}a^i(i-1)^k\&=sum_{i=1}^na^i[i^k-(i-1)^k]-a^{n+1}n^k\&=sum_{i=1}^na^isum_{j=1}^kinom k j(-1)^{j+1}i^{k-j}-a^{n+1}n^k\&=sum_{j=1}^kinom k j(-1)^{j+1}sum_{i=1}^na^ii^{k-j}-a^{n+1}n^k\&=sum_{j=1}^kinom k j(-1)^{j+1}F_{k-j}(n)-a^{n+1}n^kend{split} ]

    并且当(k=0)时,(F_0(n)=sum_{i=1}^na^i=frac{1-a^{n}}{1-a}),可以快速计算。通过这个递推关系我们可以(O(k^2))计算出(F_k(n))。用一个任意模数 NTT 说不定可以优化到(O(klog_2k))

    说不定就是说我也没试过

    100 pts

    考虑一个(f_k(n))的不存在求和符的递推式:

    [egin{split}f_k(n)-f_k(n-1)&=n^k+F_k(n-1)\f_k(n)&=2f_k(n-1)+n^k-(n-1)^kend{split} ]

    尝试构造出一个(g_k(n))满足:

    [f_k(n)+g_k(n)=2(f_k(n-1)+g_k(n-1)) ]

    显然一个可行的解为:

    [g_k(n)=2g_k(n-1)-n^k+(n-1)^k ]

    即可以解出:

    [f_k(n)=2^{n-1}(1+g_k(1))-g_k(n) ]

    这也就意味着:

    [F_k(n)=-a^{n-1}(g_k(n)+n^k)+(g_k(1)+1) ]

    定义(G_k(n)=-2(g_k(n)+n^k)),我们就有:

    [F_k(n)=a^nG_k(n)-aG_k(1) ]

    又可以通过(g_k(n))的通项公式推导得出:

    [G_k(1)=2G_k(0) ]

    于是就可以得到

    [F_k(n)=a^nG_k(n)-G_k(0) ]

    根据某些奇奇怪怪的东西我们可以知道(G_k(n))是关于(n)的不超过(k)次的多项式。

    i.e.作者自己也不会证明

    这就意味着,我们只需要知道了(G_k(n))(k+1)个点值,就可以为所欲为使用拉格朗日插值法计算出(G_k)在任何一个位置的点值。

    另外,我们不难写出如下式子:

    [G_k(n)=2^nG_k(0)+2^nF_k(n) ]

    这意味着,我们只需要计算出(F_k(n)),我们就可以得到(G_k(n))。鉴于 (F_k(n))有其简洁的递推形式,我们可以快速而方便地得到(G_k(1)dots G_k(k+1))(G_k(0))的关系。

    考虑到如下的高阶差分公式:

    [Delta^kf(k)=sum_{i=0}^kinom{k}{i}(-1)^{k-i}f(k+i) ]

    推导难度不大,自行定义算子(mathrm Rf(n)=f(n+1))并重定义差分,展开式子即可。

    不过有一个很有用的性质——任何(k)次的多项式在经过(k+1)次的差分后,结果必然是(0)

    这就是很像是求导了(差分本身就可以理解为离散中的导)。于是应有:

    [Delta^{k+1}G_k(0)=sum_{i=0}^{k+1}inom{k+1}{i}(-1)^{k+1-i}G_k(i)=0 ]

    根据这个式子我们可以列出(G_k(1)dots G_k(k+1))关于(G_k(0))的关系。而(G_k(1)dots G_k(k+1))本身可以使用(G_k(0))表示,因而我们就相当于得到了一个关于(G_k(0))的一次方程,直接解出(G_k(0))即可。

    另外,还有一种列方程的方法。我们直接利用拉格朗日插值,可以得出:

    [G_k(0)=sum_{i=1}^{k+1}G_k(i)prod_{j ot=i}frac{-j}{(i-j)} ]

    这同样是(G_k(1)dots G_k(k+1))(G_k(0))的关系,解方程就好。

    进而我们可以得到(G_k(1)dots G_k(k+1)),然后就可以插值得到(G_k(n)),最后计算出(F_k(n))(f_k(n))

    时间复杂度(O(k)sim O(klog_2m)),其中(m)为模数。

    感觉......本题的核心在其数学构造。 60 pts ~ 80 pts 中出现的错位相减是一个常见的推前缀和通项的方法。但是考场上好像把减的对象搞错了。 100 pts 中的构造很巧妙。对于(G_k(n))的多项式性质似乎有点 “ 猜想 ” 的意味,直接证明好像有难度,如果可以证明可以评论,谢谢。

    代码

    #include <cstdio>
    
    const int mod = 1e9 + 7, phi = mod - 1;
    const int MAXK = 1e6 + 5;
    
    template<typename _T>
    void read( _T &x )
    {
    	x = 0;char s = getchar();int f = 1;
    	while( s > '9' || s < '0' ){if( s == '-' ) f = -1; s = getchar();}
    	while( s >= '0' && 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 ) + 1; }
    	if( 9 < x ){ write( x / 10 ); }
    	putchar( x % 10 + '0' );
    }
    
    int inv( const int );
    int qkpow( int, int );
    
    struct func
    {
    	int a, b;
    	func() { a = b = 0; }
    	func( const int C ) { a = 0, b = C; }
    	func( const int A, const int B ) { a = A, b = B; }
    	func operator + ( const func &g ) const { return func( ( a + g.a ) % mod, ( b + g.b ) % mod ); }
    	func operator - ( const func &g ) const { return func( ( a - g.a + mod ) % mod, ( b - g.b + mod ) % mod ); }
    	func operator * ( const func &g ) const { return func( ( 1ll * a * g.b % mod + 1ll * b * g.a % mod ) % mod, 1ll * b * g.b % mod ); }
    	func operator / ( const int &g ) const { int t = inv( g ); return func( 1ll * a * t % mod, 1ll * b * t % mod ); }
    	void operator += ( const func &g ) { *this = *this + g; }
    	void operator -= ( const func &g ) { *this = *this - g; }
    	void operator *= ( const func &g ) { *this = *this * g; }
    	void operator /= ( const int &g ) { *this = *this / g; }
    	
    	int getX( const int y ) const { return 1ll * ( y - b + mod ) * inv( a ) % mod; }
    	int getY( const int x ) const { return ( 1ll * a * x % mod + b ) % mod; }
    };
    
    func coe[MAXK];
    int G[MAXK], neg[MAXK];
    int fac[MAXK], finv[MAXK];
    int prime[MAXK], pn, pwk[MAXK];
    char S[MAXK];
    int N, K;
    bool isPrime[MAXK];
    
    int qkpow( int base, int indx )
    {
    	int ret = 1;
    	while( indx )
    	{
    		if( indx & 1 ) ret = 1ll * ret * base % mod;
    		base = 1ll * base * base % mod, indx >>= 1;
    	}
    	return ret;
    } 
    
    int C( const int n, const int m ) { return 1ll * fac[n] * finv[m] % mod * finv[n - m] % mod; }
    int inv( const int n ) { return n <= K + 10 ? 1ll * finv[n] * fac[n - 1] % mod : qkpow( n, mod - 2 ); }
    
    void EulerSieve( const int siz )
    {
    	pwk[1] = 1;
    	for( int i = 2 ; i <= siz ; i ++ )
    	{
    		if( ! isPrime[i] ) prime[++ pn] = i, pwk[i] = qkpow( i, K );
    		for( int j = 1 ; j <= pn && 1ll * i * prime[j] <= siz ; j ++ )
    		{
    			isPrime[i * prime[j]] = true, pwk[i * prime[j]] = 1ll * pwk[i] * pwk[prime[j]] % mod;
    			if( ! ( i % prime[j] ) ) break;
    		}
    	}
    }
    
    void init( const int siz )
    {
    	fac[0] = 1;
     	EulerSieve( siz );
    	for( int i = 1 ; i <= siz ; i ++ ) fac[i] = 1ll * fac[i - 1] * i % mod;
    	finv[siz] = qkpow( fac[siz], mod - 2 );
    	for( int i = siz - 1 ; ~ i ; i -- ) finv[i] = 1ll * finv[i + 1] * ( i + 1 ) % mod;
    }
    
    int Lagrange( const int n )
    {
    	#define M K + 1
    	if( n <= M ) return G[n];
    	neg[0] = 1; int up = 1, ans = 0;
    	for( int i = 2 ; i <= M ; i ++ ) up = 1ll * up * ( n - i ) % mod;
    	for( int i = 1 ; i <= M ; i ++ ) neg[i] = 1ll * neg[i - 1] * inv( mod - i ) % mod;
    	for( int i = 1 ; i <= M ; i ++ )
    	{
    		ans = ( ans + 1ll * up * finv[i - 1] % mod * neg[M - i] % mod * G[i] % mod ) % mod;
    		if( i < M ) up = 1ll * up * ( n - i ) % mod * inv( n - i - 1 ) % mod;
    	}
    	return ans;
    	#undef M
    }
    
    int main()
    {
    	int ind = 0;
    	scanf( "%s %d", S, &K );
    	for( int i = 0 ; S[i] ; i ++ )
    		N = ( 10ll * N + S[i] - '0' ) % mod,
    		ind = ( 10ll * ind + S[i] - '0' ) % phi;
    	init( K + 10 ), coe[0] = func( 1, 0 );
    	int F = 0, alph = inv( 2 ), pwa = 1, pw2 = 2;
    	for( int i = 1 ; i <= K + 1 ; i ++ )
    	{
    		F = ( F + 1ll * pwa * pwk[i - 1] % mod ) % mod;
    		coe[i] = func( pw2, 1ll * pw2 * F % mod );
    		pwa = 1ll * pwa * alph % mod, pw2 = 2ll * pw2 % mod;
    	}
    	func tmp;
    	for( int i = 0 ; i <= K + 1 ; i ++ )
    	{
    		if( ( K + 1 - i ) & 1 ) tmp -= coe[i] * C( K + 1, i ); 
    		else tmp += coe[i] * C( K + 1, i );
    	}
    	G[0] = tmp.getX( 0 );
    	for( int i = 1 ; i <= K + 1 ; i ++ )
    		G[i] = coe[i].getY( G[0] );
    	int GN = Lagrange( N );
    	int FN = ( 1ll * qkpow( 2, phi - ind ) * GN % mod - G[0] + mod ) % mod;
    	write( ( qkpow( N, K ) + 1ll * FN * qkpow( 2, ind - 1 + phi ) % mod ) % mod ), putchar( '
    ' );
    	return 0;
    }
    
  • 相关阅读:
    Spring Boot+Vue 项目前端_Vuejs环境搭建
    vue+element 数据表格分页
    vue+elementui 实现新增和修改共用一个弹框
    Vue删除数据成功后如何刷新表格数据
    vue中使用echart绑定点击事件进行路由切换
    Vue的生命周期
    添加访问量插件
    Docker部署的Gitlab平行迁移到另一台服务器上
    GLEAN: Generative Latent Bank for Large-Factor Image Super-Resolution【阅读笔记】
    Learning Continuous Image Representation with Local Implicit Image Function【阅读笔记】
  • 原文地址:https://www.cnblogs.com/crashed/p/13355642.html
Copyright © 2020-2023  润新知