JZOJ4720. 地下的太阳

题目大意

nm 个火炉,排成 m 层,每层恰好n个火炉。
初始时第一层第 i 个炉的火力为i,然后火力从上而下传递。设 energy[i][j] 为第 i 层,第j个火炉的能量,那么满足

energy[i][j]=k=1n(energy[i1][k]+ji)

然后从当前最后一层的有火力的火炉中,把编号最前的 s 个火炉和最后的s个火炉单独取出(取过的火炉没有火力,即不会再被取),设火炉火力值集合分别为 A B,然后把 A B集合的火炉分别按原来编号升序排序,得到的能量为:
i=1sj=1s( AiBj(|ij|+1)2 )

一共进行 T 操作,每次操作输出最后得到的火力,答案对1004535809取模。

Data Constraint
T10,n1018,m5000,s30000

题解

先化简一下式子

energy[i][j]=k=1n(energy[i1][k]+ji)=nji+k=1nenergy[i1][k]

Sumi=j=1nenergy[i][j]

energy[i][j]=nji+Sumi1

Sumi=j=1nenergy[i][j]=j=1n(nji+Sumi1)=n(Sumi1+j=1nji)

然后我们可以 O(m2) 预处理 Si=nj=1ji ,计算出 Summ ,再 O(slogn) 取出 A,B 的元素。


自然数幂和

计算 Si=nj=1ji 就是求自然数幂和,这里简单讲一种 O(n2) 的方法。
已知

(a+b)k+1=i=0k+1Cik+1aibk+1i

(n+1)k+1nk+1=C1k+1nk+C2k+1nk1+...+Ckk+1n+1

分别令 n=1,2,3,...,n 代入后将 n 个式子相加,得到
(n+1)k+11=C1k+1i=1nik+C2k+1i=1nik1+...+Ckk+1i=1ni+n

移项得
i=1nik=1k+1[(n+1)k+1(C2k+1i=1nik1+...+Ckk+1i=1ni+n+1)]

S(n,k)=ni=1ik ,原式即为
S(n,k)=1k+1[(n+1)k+1(C2k+1S(n,k1)+...+Ckk+1S(n,1)+n+1)]

然后就能递归求解了。


现在取出了 A B,剩下的问题就是如何快速统计答案。观察式子:

i=1sj=1s( AiBj(|ij|+1)2 )

(|ij|+1)2=(ij)2+1+2|ij|=i2+j22ij+1+2|ij|

前四项的计算都比较简单,以 i2 为例:
将式子变形:
i=1sj=1sAiBji2=i=1sAii2j=1sBj

这样一来就比较容易计算了,维护对应次数的和即可,其他三项同理。
剩下的绝对值,先讨论 ij 的情况。此时
i=1sj=1iAiBj2(ij)

拆开括号有两项: 2i2j ,以 2i 为例
i=1sAii2j=1iBj

维护 B 的前缀和就很好计算了。
ji的情况也同理。

SRC

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std ;

#define N 5000 + 10
#define M 30000 + 10
typedef long long ll ;
const int MO = 1004535809 ;

ll fac[N] , _fac[N] ;
ll S[N] , Sum[N] ;
ll A[M] , B[M] ;
ll sA[M] , sa1[M] , sB[M] , sb1[M] ;
int Case , m , len ;
ll n , ans ;

ll Power( ll x , int k ) {
    ll s = 1 ;
    x %= MO ;
    while ( k ) {
        if ( k & 1 ) s = s * x % MO ;
        x = x * x % MO ;
        k /= 2 ;
    }
    return s ;
}

void Pre() {
    fac[0] = _fac[0] = 1 ;
    for (int i = 1 ; i <= 5000 ; i ++ ) {
        fac[i] = fac[i-1] * i % MO ;
        _fac[i] = Power( fac[i] , MO - 2 ) ;
    }
}

ll Calc( int n , int m ) { return fac[n] * _fac[m] % MO * _fac[n-m] % MO ; }

void GetS( int k ) {
    if ( k == 1 ) {
        S[k] = n % MO * ((n + 1) % MO) % MO * _fac[2] % MO ;
        return ;
    }
    GetS( k - 1 ) ;
    S[k] = Power( (n + 1) % MO , k + 1 ) ;
    ll sum = (n + 1) % MO ;
    for (int i = 2 ; i <= k ; i ++ )
        sum = (sum + Calc(k+1,i) * S[k+1-i] % MO) % MO ;
    S[k] = (S[k] - sum + MO) % MO ;
    S[k] = S[k] * Power( k + 1 , MO - 2 ) % MO ;
}

int main() {
    scanf( "%lld%d" , &n , &m ) ;
    scanf( "%d" , &Case ) ;
    Pre() ;
    GetS(m) ;
    Sum[1] = S[1] ;
    for (int i = 2 ; i <= m ; i ++ )
        Sum[i] = (n % MO) * ((Sum[i-1] + S[i]) % MO) % MO ;
    int last = 0 ;
    while ( Case -- ) {
        A[0] = B[0] = ans = 0 ;
        scanf( "%d" , &len ) ;
        for (ll i = last + 1 ; i <= last + len ; i ++ ) A[++A[0]] = (Sum[m-1] + n % MO * Power(i,m) % MO) % MO ;
        for (ll i = n - last - len + 1 ; i <= n - last ; i ++ )
            B[++B[0]] = (Sum[m-1] + n % MO * Power(i,m) % MO) % MO ;
        ll suma = 0 , suma1 = 0 , suma2 = 0 ;
        ll sumb = 0 , sumb1 = 0 , sumb2 = 0 ;
        for (int i = 1 ; i <= len ; i ++ ) {
            suma = (suma + A[i]) % MO ;
            suma1 = (suma1 + A[i] * i % MO) % MO ;
            suma2 = (suma2 + A[i] * i % MO * i % MO) % MO ;
            sumb = (sumb + B[i]) % MO ;
            sumb1 = (sumb1 + B[i] * i % MO) % MO ;
            sumb2 = (sumb2 + B[i] * i % MO * i % MO) % MO ;
            sA[i] = (sA[i-1] + A[i]) % MO ;
            sa1[i] = (sa1[i-1] + 2ll * A[i] % MO * i % MO) % MO ;
            sB[i] = (sB[i-1] + B[i]) % MO ;
            sb1[i] = (sb1[i-1] + 2ll * B[i] % MO * i % MO) % MO ;
        }
        ans = (ans + suma * sumb % MO) % MO ;
        ans = (ans + suma * sumb2 % MO) % MO ;
        ans = (ans + suma2 * sumb % MO) % MO ;
        ans = (ans - 2ll * suma1 % MO * sumb1 % MO + MO) % MO ;
        for (int i = 1 ; i <= len ; i ++ ) {
            ans = (ans + 2ll * i * A[i] % MO * sB[i] % MO) % MO ;
            ans = (ans - A[i] * sb1[i] % MO + MO) % MO ;
            ll tp = (sB[len] - sB[i] + MO) % MO ;
            ans = (ans - 2ll * i * A[i] % MO * tp % MO + MO) % MO ;
            tp = (sb1[len] - sb1[i] + MO) % MO ;
            ans = (ans + A[i] * tp % MO) % MO ;
        }
        printf( "%lld\n" , ans ) ;
        last += len ;
    }
    return 0 ;
}

以上.

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值