CodeForces 575 A.Fibonotci(矩阵快速幂+线段树)

59 篇文章 0 订阅
52 篇文章 0 订阅

Description
F[0]=0,F[1]=1,F[n]=s[n-2]*F[n-2]+s[n-1]*F[n-1],n>=2,s序列是一个循环节为n的序列,但是有m个s[i]的值不等于s[i%n] (i>=n),求F[K]%p
Input
第一行两个整数K和p表示求F[K]%p的值,然后输入一整数n表示s序列循环节,之后输入n个整数表示s[0]~s[n-1],再输入一整数m表示s序列中m个奇异点个数,最后输入m对整数x,y表示s[x]=y
(1<=n,m<=5e4,0<=K<=1e18,1<=p<=1e9,1<=s[i],y<=1e9,n<=x<=1e9)
Output
输出F[K]%p的值
Sample Input
10 8
3
1 2 1
2
7 3
5 4
Sample Output
4
Solution
这里写图片描述
这里写图片描述,问题变成求K-1个矩阵的乘积,这些矩阵本来应该是由A(0),…,A(n-1)组成,但是由于那些奇异点的存在使得中间有一部分矩阵发生了变化,假设变化后的矩阵为B(i),如果改变s(i)的值,那么会影响到B(i-1)和B(i),求出这些变化的矩阵以及变化的位置,那么这个矩阵序列就变成若干正常的由A(0)~A(n-1)组成的段以及这些奇异点,对于每一正常段,由于都是由循环A(0)~A(n-1)循环得到的,用线段树维护一下A(0),…,A(n-1),A(0),…,A(n-1) 区间积(之所以对两倍的A序列维护是因为起点不一定是A(0)),那么对于一个正常段[L,R),如果R-L>=n,说明该段出现了至少一段完整的A(0)~A(n-1),分成两部分去处理,第一部分是前面的循环部分,设k=(R-L)/n,r=(R-L)%n,求出一段的矩阵积为C=query(L%n,L%n+n-1),然后对该结果做矩阵快速幂C=C^k,剩余的一段为D=query(R%n+n-r,R%n+n-1),那么这块的结果就是C*D;如果R-L < n,若L%n < R%n,那么答案就是query(L%n,R%n-1),否则答案就是(L%n,R%n+n-1),知道了每一个正常段的答案和分隔开正常段的奇异点的值,按顺序乘起来即为最终答案
Code

#include<cstdio>
#include<cstring>
#include<map>
using namespace std;
typedef long long ll;
#define maxn 111111
typedef long long ll;
int n,m;
ll K,p,s[maxn]; 
map<ll,ll>M;
map<ll,ll>::iterator it;
struct Mat
{
    ll mat[2][2];//矩阵 
    Mat()
    {
        memset(mat,0,sizeof(mat));
    }
    Mat operator *(const Mat &b)const
    {
        Mat ans;
        for(int i=0;i<2;i++)        
            for(int k=0;k<2;k++)
                for(int j=0;j<2;j++)
                    ans.mat[i][j]=(ans.mat[i][j]+mat[i][k]*b.mat[k][j]%p)%p;
        return ans;
    }
};
Mat mod_pow(Mat a,ll k)//矩阵快速幂 
{
    Mat ans;
    ans.mat[0][0]=ans.mat[1][1]=1;
    while(k)
    {
        if(k&1)ans=ans*a;
        a=a*a;
        k>>=1;
    }
    return ans;
}
Mat Get_M(ll x,ll y)
{
    Mat ans;
    ans.mat[0][0]=0,ans.mat[0][1]=x,
    ans.mat[1][0]=1,ans.mat[1][1]=y;
    return ans;
}
ll Get_S(ll x)
{
    if(M.find(x)!=M.end())return M[x];
    return s[x%n];
}
Mat A[maxn<<2];
#define ls (t<<1)
#define rs ((t<<1)|1)
void push_up(int t)
{
    A[t]=A[ls]*A[rs];
}
void build(int l,int r,int t)
{
    if(l==r)
    {
        A[t]=Get_M(s[l],s[l+1]);
        return ;
    }
    int mid=(l+r)/2;
    build(l,mid,ls),build(mid+1,r,rs);
    push_up(t);
}
Mat query(int L,int R,int l,int r,int t)
{
    if(L<=l&&r<=R)return A[t];
    int mid=(l+r)/2;
    Mat ans;
    ans.mat[0][0]=ans.mat[1][1]=1;
    if(L<=mid)ans=ans*query(L,R,l,mid,ls);
    if(R>mid)ans=ans*query(L,R,mid+1,r,rs);
    return ans;
}
Mat Query(ll L,ll R)
{
    Mat ans,temp;
    if(R-L>=n)
    {
        ll k=(R-L)/n,r=(R-L)%n;
        temp=query(L%n,L%n+n-1,0,2*n-1,1);
        ans=mod_pow(temp,k);
        if(r)ans=ans*query(R%n+n-r,R%n+n-1,0,2*n-1,1);
    }
    else
    {
        if(L%n<R%n)ans=query(L%n,R%n-1,0,2*n-1,1);
        else ans=query(L%n,R%n+n-1,0,2*n-1,1);
    }
    return ans;
}
ll pos[maxn];
Mat B[maxn];
int main()
{
    while(~scanf("%I64d%I64d",&K,&p))
    {
        M.clear();
        scanf("%d",&n);
        for(int i=0;i<n;i++)
            scanf("%d",&s[i]),s[i+n]=s[i];
        scanf("%d",&m);
        for(int i=0;i<m;i++)
        {
            ll x,y;
            scanf("%I64d%I64d",&x,&y);
            M[x]=y;
        }
        if(K==0||p==1)
        {
            printf("0\n");
            continue;
        }
        int cnt=0;
        for(it=M.begin();it!=M.end();it++)
        {
            ll x=it->first,y=it->second;
            if(!cnt||x>pos[cnt-1]+1)
            {
                if(x-1<K-1)pos[cnt]=x-1,B[cnt++]=Get_M(Get_S(x-1),y);
            }
            if(x<K-1)pos[cnt]=x,B[cnt++]=Get_M(y,Get_S(x+1));
        }
        pos[cnt]=K-1,B[cnt++]=Get_M(Get_S(K-1),Get_S(K));
        build(0,2*n-1,1);
        Mat ans;
        ans.mat[0][0]=ans.mat[1][1]=1;
        ll pre=0;
        for(int i=0;i<cnt;i++)
        {
            if(pos[i]>pre)ans=ans*Query(pre,pos[i]);
            ans=ans*B[i];
            pre=pos[i]+1;
        }
        printf("%I64d\n",ans.mat[1][0]);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值