[JZOJ5591]. 修修的铁拳

题目描述

给出初始点(x0,y0),你可以走T步,每次上下左右,最终你会走到一个点(x,y),这个点的贡献是 xnym x n y m ,问所有方案的贡献和。
这里写图片描述

解题思路

考虑40分怎么做,可以枚举一个(x,y),算出到这里的方案数,然后乘上贡献。
另一个思路是dp地维护第i步的贡献和。
考虑某个方案往左右走,(x-1,y)+(x+1,y)的贡献,二项式展开 ((x+1)n+(x1)n)ym=ymi=0..n,i0(mod 2)2Cninxni ( ( x + 1 ) n + ( x − 1 ) n ) y m = y m ∑ i = 0.. n , i ≡ 0 ( m o d   2 ) 2 C n n − i x n − i ,奇数次数的项都被消掉了。我们可以通过这种办法来维护出新的(x,y)的贡献和。
具体地,设f[t][i][j]表示走了t步,对于当前所有方案(x,y)们, xiyj x i y j 的和是多少。由于二项式定理的特性,而且和互相不干扰,我们可以把所有x,y放到一起存,存次数小于n,m的项是为了能够维护出 xnym x n y m 。x和y实际可以分开维护,因为我们可以在最后组合起来。
我们现在的任务是得出 xn x n 的和。设f[i][j]表示走了i步, xj x j 的和是多少。
转移 f[i][j]=k0(mod 2)2Ckjf[i1][jk] f [ i ] [ j ] = ∑ k ≡ 0 ( m o d   2 ) 2 C j k f [ i − 1 ] [ j − k ] ,微观地,就是(x,y)变成了(x-1,y),(x+1,y)。
我们分别得出 fx[i][n] f x [ i ] [ n ] fy[j][m] f y [ j ] [ m ] 之后,枚举左右走i次,那么上下走t-i次,然后就可以用两个f组合起来算答案了, ans=if[i][n]f[ti][m]Cit a n s = ∑ i f [ i ] [ n ] ∗ f [ t − i ] [ m ] ∗ C t i
不过t太大,这样没有办法做。
一种思路是旋转45度,这样x,y可以看成一样的东西,搞矩阵乘法优化dp之后求答案。这样能通过80%
考虑转移特性。对于原本的一个f[0][j],我们观察它会被哪些f状态计算,即它会被怎么转移出去。发现转移到下一层,有时候他会乘2然后下标不变,即f[i][j]->f[i+1][j],那么t这么大而n这么小,肯定很多时候都下标不变,更关键的是,他在任何地方停留,转移系数都是2,那么我们不考虑停留在原地的贡献,只计算下标变大的转移,设次数cnt,最后再乘上 2tcnt 2 t − c n t ,那么我们就把层数优化到了n的级别。
仍然把最终做出来的东西叫 fxfy f x 和 f y ,枚举左右的 cntx c n t x 和上下的 cnty c n t y ,要还原原来的 fx[n]fy[m] f x [ n ] 和 f y [ m ] ,还需要乘上 4tcntxcnty 4 t − c n t x − c n t y ,表示下标不变的转移,因为既可能是左右,也可能是上下,所以不是2而是4。然后再乘上操作顺序的组合数就得到答案了。
时间复杂度 O(n3) O ( n 3 ) 左右

代码

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<map>
#include<set>
using namespace std;
typedef long long ll;
typedef double db;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
#define cmax(a,b) (a=(a>b)?a:b)
#define cmin(a,b) (a=(a<b)?a:b)
const int N=2e5+5,M=205,mo=1e9+7;
int fac[N],rev[N],x,y,n,m,t,ans,i,j,k,tmp,f[M][M],g[M][M];
int ksm(int x,int y)
{
    int ret=1;
    x%=mo;
    while (y)
    {
        if (y&1) ret=1ll*ret*x%mo;
        y>>=1;
        x=1ll*x*x%mo;
    }
    return ret;
}
void predo(int n)
{
    fac[0]=1;
    fo(i,1,n) fac[i]=1ll*fac[i-1]*i%mo;
    rev[n]=ksm(fac[n],mo-2);
    fd(i,n,1) rev[i-1]=1ll*rev[i]*i%mo;
}
int c(int n,int m)
{
    if (m<=1e5)
        return 1ll*fac[m]*rev[n]%mo*rev[m-n]%mo;
    int ret=1,i;
    fd(i,m,m-n+1)
        ret=1ll*ret*i%mo;
    return 1ll*ret*rev[n]%mo;
}
int main()
{
    freopen("t1.in","r",stdin);
    //freopen("t1pai.out","w",stdout);
    scanf("%d %d %d %d %d",&x,&y,&t,&n,&m);
    predo(1e5);
    fo(i,0,n) f[0][i]=ksm(x,i);
    fo(i,0,m) g[0][i]=ksm(y,i);
    fo(i,1,n)
        fo(j,0,n)
            for(k=j-2;k>=0;k-=2) 
                f[i][j]=(f[i][j]+2ll*c(k,j)*f[i-1][k])%mo;
    fo(i,1,m)
        fo(j,0,m)
            for(k=j-2;k>=0;k-=2) 
                g[i][j]=(g[i][j]+2ll*c(k,j)*g[i-1][k])%mo;
    ans=0;
    fo(i,0,min(n+m,t))
    {
        tmp=0;
        fo(j,0,i) if (j<=n&&i-j<=m)
            tmp=(tmp+1ll*f[j][n]*g[i-j][m]%mo*c(j,i))%mo;
        ans=(ans+1ll*c(i,t)*tmp%mo*ksm(4,t-i))%mo;
    }
    ans=(ans+mo)%mo;
    printf("%d\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值