小红帽的画笔(NOIP模拟赛Round 7)

又到了神奇的模拟赛时间~

真是丧~

好吧我们来看看题目

小红帽是Pop star上最著名的人类画家,她可以将任何画出的东西变成真实的物品。赋予她这样神奇能力的正是她手上的画笔。

小红帽每次作画时,都需要用到她的调色盘,我们把每个自然数都对应一种颜色,那么小红帽的调色盘就可以看成是一个斐波那契数列(数列第12项都为1,小红帽每次需要一种颜色时,她都会用画笔蘸取一段区间,得到的颜色就是区间里所有的数之和。

受到秋之国人民的邀请,小红帽要为他们画一个夏天。小红帽要进行n次取色,给出每次蘸取的区间[l,r],作为小C委派来进行记录的你需要输出每次小红帽得到的颜色,答案对mod取模

【数据范围】

对于10%的数据,n<=100,l,r<=10^4;

对于30%的数据,l,r<=10^7;

对于90%的数据,mod<=10^9;

对于100%的数据,0<=n<=1000,1<=l<=r<=10^18,0<mod<=10^18。

————————————————我是分割线————————————————————

很显然,这道题目就是求斐波那契数列前r项的前缀和减去前l-1项的前缀和,普通的DP都可以做到求斐波那契数列,但是很显然10^18我们就会T

在此我们讲讲矩阵乘法

矩阵乘法,顾名思义,就是2个矩阵相乘。具体如下

所以呢我们如果要算斐波那契数列的第N项只需要将图中的矩阵自乘n-2次,再乘第一个矩阵,得到的矩阵的第一个数就是答案

那么我们又怎样求前缀和呢?

在此有两种方法供参考

TOP1:找规律

我们假设a,b为斐波那契数列的第一项和第二项

那么我们很显然就可以递推出后面的几项

那么这有什么规律呢?

很快就发现了规律

a=a+b-b;a+b=a+2b-b;2a+2b=2a+3b-b;3a+4b=3a+5b-b.....

所以我们只需要求num[r+2]-1-(num[l-1+r]-1)=num[r+2]-num[l-1]即可啦

TOP2:构造矩阵

显然我们知道我们要保留答案矩阵的前面2个数,而我们想办法构造出第三个数,用于计算前缀和。这样将这个矩阵自乘n-2次,输出第三个数就好啦。

然后我们会想到我们的前缀和就是sum[i]=sum[i-1]+num[i];

然后就会构造出这个矩阵啦

———————————————我是分割线—————————————————

那么我们还看到一个问题,如何处理mod?

我们知道如果mod为10^18

那么一次乘法操作的数会达到10^36

如果是这样我们就需要做高精除+高精乘了。

但是有没有更快的方法?

首先我们知道如果在加法中进行取余,结果不改变。

所以我们将乘法转变为加法

这样速度虽然慢了点,却不会爆long long

然后这道题就愉快解决啦!

下面贴代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n;
struct matrix{
    unsigned long long mat[2][2];
};
unsigned long long l,r,mod;
long long mul(long long x,long long y)
{
    if (x<y) swap(x,y);
    register long long z=0;
    for (;y;y>>=1,x<<=1,x=x>=mod?x-mod:x) if (y&1) z+=x,z=z>=mod?z-mod:z;
    return z;
}
matrix multiply(matrix a,matrix b)
{
    unsigned long long sum=0;
    matrix c;
    memset(c.mat,0,sizeof(c.mat));
    for(int i=0;i<=1;i++)
        for(int j=0;j<=1;j++)
        {
            sum=0;
            for(int k=0;k<=1;k++)
            {
                long long numqq=mul(a.mat[i][k],b.mat[k][j]);
                if(mod>1000000000)sum+=numqq,sum%=mod;
                else sum+=(a.mat[i][k]*b.mat[k][j])%mod;
            }
            c.mat[i][j]=sum;
        }
    return c;
}
matrix matmod(matrix a,unsigned long long k)
{
    matrix res;
    memset(res.mat,0,sizeof(res.mat));
    for(int i=0;i<=1;i++)res.mat[i][i]=1;
    while(k)
    {
        if(k&1)res=multiply(res,a);
        k>>=1;
        a=multiply(a,a);
    }
    return res;
}
unsigned long long work(unsigned long long x)
{
    if(x==0)return 0;
    if(x==1)return 1;
    if(x==2)return 2;
    matrix a,b;
    memset(a.mat,0,sizeof(a.mat));
    memset(b.mat,0,sizeof(b.mat));
    for(int i=0;i<=1;i++)a.mat[i][1]=1;
    for(int i=0;i<=1;i++)b.mat[i][0]=1;
    a.mat[1][0]=1;
    a=matmod(a,x);
    a=multiply(a,b);
    return (a.mat[1][0]-1+mod)%mod;
}
int main(){
    freopen("artist.in","r",stdin);
    freopen("artist.out","w",stdout);
    scanf("%d%lld",&n,&mod);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld%lld",&l,&r);
        unsigned long long num1=0,num2=0;
        num1=work(l-1);
        num2=work(r);
        unsigned long long ans=num2-num1+mod;
        printf("%lld\n",ans%mod);
    }
    return 0;
    fclose(stdin);
    fclose(stdout);
} 
时间: 2025-01-12 05:49:26

小红帽的画笔(NOIP模拟赛Round 7)的相关文章

YYH的营救计划(NOIP模拟赛Round 6)

原题传送门 这道题目有2种做法: 1.kruskal 2.二分 对于第一种算法,我们知道最小的路一定在最小生成树上.这道题的原理可同NOIP货车运输 对于第二种算法,我们发现这道题的答案具有结论单调性,所以我们可以二分答案,然后用链表处理即可.. 下面贴第一种算法的代码 #include<iostream> #include<cstdio> #include<algorithm> using namespace std; int n,m,s,t; struct edge

水(NOIP模拟赛Round #10)

题目描述: 小Z有一个长度为的数列.他有次令人窒息的操作,每次操作可以使某个数字或.他当然是希望这些数字的乘积尽量小了.为了简化题目,你只需输出操作完成后的数列即可. ----------------我是分割线---------------- 这道题目,我们可以先自己手动模拟一遍,就能发现,首先我们需要尽量让乘积最小,那么首先我们希望乘积为负数, 所以假设一开始的时候乘积为整数,我们先拿出绝对值最小的那个数,如果是正数就-x,如果是负数就+x直到乘积变为负数. 在乘积变为负数之后,我们来看一个问

number(NOIP模拟赛Round 3)

好吧,神奇的题目.. 原题传送门 这道题目,神奇的字符单调栈.. 首先预处理出1~n个数(大家都会.) 然后塞进字符串里,输出答案(水~) 然后.. 我们需要将所有的字符存入单调栈中,然后乱搞,就可以输出啦. 不过注意:有的题目可能在单调栈中没有把所有的字符删完,即后面的所有字符都一样,那么此时我们需要直接把后面的多出来的位数减去即可. 还有,单调栈中如果已经减了m位之后就不能再减了 在维护单调队列时我们要尽量让排名靠前的数字被删除(贪心) 这样得到的答案才最优 (这道题我一开始做的时候没有维护

calc(NOIP模拟赛Round 3)

原题: D e s c r i p t i o n 给三个正整数n,m和p,求(n^1+...n^m) mod p. Input 一行,三个整数n,m和p. Output 输出答案. S a m p l e  I n p u t 2 2 5 S a m p l e  O u t p u t 1 数 据 范 围 n,p<=10^8    m<=10^17 时 限 1s 首先看到m范围就知道是快速幂了对吧. 然后我们想想看.假如是平常的快速幂的话时间复杂度为O(M) TLE了对吧,然后我们想想有没

YYH的球盒游戏(NOIP模拟赛Round 6)

原题传送门 这题的算法优化真是博大精深. 具体我们一个一个来讲. 我们先看这道题的算法,(这..裸的费用流啊...) 然后我们发现负边(好吧,其实没有什么用.spfa可以跑) 首先所有的球都要向源点连费用0,流量为球的数量的边 然后我们考虑每一个球都要到汇点,所以从盒子向汇点连费用0,流量inf的边. 对于每一个球向每一个盒子连费用为-c(c为收益),流量为1的边. 重点来了:怎么处理多放? 我们已知一个公式 n^2-(n-1)^2=n*2-1; 所以我们从每一个盒子向汇点连流量为1,费用为2*

panel(NOIP模拟赛Round 4)

原题传送门 好吧,,这道题..本来以为挺难的.打了个暴力bfs+hash(期望得分30,实际得分30) 奇特的是,这道题如果不用hash(期望得分20,实际得分100),好吧数据实在是太水了(不会T吗?) 然后我们寻找正解 在此膜一下巨神学弟clz 著名的clz告诉我们,我们只需要暴力枚举第一行的情况,然后可以直接O(n^7m)得出结果?? 因为我们知道,假设第一行的情况是确定的,并且我们已经递归到了第二行的第i个按钮. 那么panel[1][i-1]若是不亮的,那么我们必须要按这个按钮,因为除

YYH的苍天大竹(NOIP模拟赛Round 6)

原题传送门 这道题我们很显然要用DP来做. 那么首先我们需要构造出一个DP方程 f[i]肯定由另一个状态dp转移后+1得到,那么这个状态是什么呢? 很明显就是mint[i]~i-k(在此mint表示的是以i为结尾的竹子最左端最多能延展到的位置) 看到这个之后我们想到了一个N^2DP 但是还是不够 我们想一想如何得到mint? mint就是判断一段区间的最大值减去最小值是否满足题意 那么我们可不可以用数据结构得出这个答案呢? 显然我们可以用线段树来优化这个过程 时间复杂度O(nlogn) 同样在得

YYH的王国(NOIP模拟赛Round 6)

原题传送门 好吧,这道题还是结论题, 我们很容易就发现如果所以点都向1连边,那么答案一定最优. 所以我们只要计算2+3+4+5+...+n即可. 记住!不要智障地用for循环! 等差数列!! #include<iostream> #include<cstdio> using namespace std; long long t,n,num; unsigned long long ans; int main(){ scanf("%d",&t); for(i

set(NOIP模拟赛Round 4)

原题传送门 这题很神奇,对吧. 标程还理解了好久,才明白. 这道题需要用状压DP.首先我们看到总共只有15个字符串,所以可以用hash存储状态. 然后我们还需要一维用来存储DP到第几个字符. 所以dp[i][j]表示填到第i位后满足字符串的状态为j的个数: 一开始我们将一个统计数定义为nj=j(j为状态); 每次更新答案的时候只要从a到z枚举一遍,如果有不符合的地方那么nj-当前字符串所对应的二进制位(nj-=1<<l) 统计答案的时候我们只需要for一遍,看看是否有一种状态的符合字符串的个数