CQU 雷神之路(DP+矩阵加速)

Time Limit: 10000 MSMemory Limit: 32768 K

雷神之路

描述

zzblack成功通关SAO,现在,等待他的将是雷神之路。雷神之路
是在x轴上的一条路。雷神在路的尽头放了无尽的宝藏。一开始,
zzblack在初始点0,每次能走一步,两步或者三步,路的尽头的点
坐标为n。雷神为了增大游戏的难度,在路上放置了m个地雷(不能
踩上去),每个地雷的坐标为,然而,zzblack破解了这个游戏,
因此他知道每个地雷的坐标。他不屑于知道能否拿到宝藏,他只想
知道有多少种方法能拿到宝藏。

输入

第一行输入一个T表示有T组样例
接下来块
每块第一行
接下来一行描述个地雷的位置
输出

输出方案数
样例输入

2
4 1
3
6 2
1 2

样例输出

3
4

思路:
还是从朴素开始:
f[i]定义为 f[i] 到达坐标i的方案数
朴素肯定是O(N)的递推:
f[i]=f[i-1]+f[i-2]+f[i-3] (如果有地雷的话,则对应f为0)

但是 N≤1e18 ,又是递推 所以很容易想到矩阵加速。
矩阵加速是在没有地雷的情况下可以直接算出f[i]

那么困难就变成了如何去掉地雷的影响
换一种方式说 地雷会产生怎样的影响
其实第i个地雷对后面的方案数造成的影响中
只有第i+1个地雷前的两个f[i-1] f[i-2]是有用的
所以我们只用算第i个地雷前面的两个个f[i-1]f[i-2]就好了。
然后f[i]=0 (是地雷嘛) 于是 f[i],f[i-1],f[i-2] 就是可以构成一个新的矩阵快速幂的起点。然后去算一下个地雷,直到算到n
复杂度 kO(m) k是矩阵快速幂的复杂度,log2(1e18)*27=60*27的样子吧= =。。

制杖思考:
一开始想了个m^2的算法用f[i]去把后面所有的地雷的方案刷一遍。。
T有500组 肯定过不了辣。。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
#define mem(array) memset(array,0,sizeof array)
const int MOD=1e9+7;

int T,n,m;
int  a[505];
long long  f[505];
long long  js(int x){
    if(x==0) return 0;
    if(x==1) return 1;
    x--;
    long long  D[3][3];
    long long  C[3][3]={1,0,0,0,1,0,0,0,1};
    long long  A[3][3]={1,1,0,1,0,1,1,0,0};
    while(x){
        if(x & 1) {
                for(int i=0;i<3;i++)
                    for(int j=0;j<3;j++)
                    {
                        D[i][j]=0;
                        for(int k=0;k<3;k++) //C[i][k]=A[k][j];
                            D[i][j]=(D[i][j]+C[i][k]*A[k][j]) % MOD;

                    }

                for(int i=0;i<3;i++)
                    for(int j=0;j<3;j++) C[i][j]=D[i][j];
        }
        for(int i=0;i<3;i++)
            for(int j=0;j<3;j++)
            {   
                D[i][j]=0;
                for(int k=0;k<3;k++)
                    D[i][j]=(D[i][j]+A[i][k]*A[k][j]) %MOD;
            }
        for(int i=0;i<3;i++)
            for(int j=0;j<3;j++) A[i][j]=D[i][j];
        x=x>>1;
    }
    return (C[0][0]+C[1][0]) % MOD;
}
int main(){
    freopen("in.txt","r",stdin);
    scanf("%d",&T);

    while(T--){
        scanf("%d %d",&n,&m);
        mem(f);mem(a);
        //cout<<js(3)<<endl;
        for(int i=1;i<=m;i++) scanf("%d",&a[i]);
        sort(a+1,a+1+m);
        for(int i=1;i<=m;i++)
        {
            f[i]+=js(a[i]);     
            for(int j=i+1;j<=m;j++)
            {
                f[j]-=f[i]*js(a[j]-a[i]) % MOD;
            }
            //cout<<f[i]<<endl;
        }   
        long long  ans=js(n);
        for(int i=1;i<=m;i++)
            if(n>a[i])
             ans=(ans-f[i]*js(n-a[i])) % MOD;
        //for(int i=1;i<=n;i++) cout<<f[i]<<
        if(ans>0 && a[m]!=n)printf("%lld\n",ans);
        else cout<<0<<endl;
    }

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值