显示标签为“解题报告-CF”的博文。显示所有博文
显示标签为“解题报告-CF”的博文。显示所有博文

CF 696B Puzzles 解题报告


Puzzles
time limit per test 1 second
memory limit per test 256 megabytes
input standard input
output standard output
Barney lives in country USC (United States of Charzeh). USC has n cities numbered from 1 through n and n - 1 roads between them. Cities and roads of USC form a rooted tree (Barney's not sure why it is rooted). Root of the tree is the city number 1. Thus if one will start his journey from city 1, he can visit any city he wants by following roads.
Some girl has stolen Barney's heart, and Barney wants to find her. He starts looking for in the root of the tree and (since he is Barney Stinson not a random guy), he uses a random DFS to search in the cities. A pseudo code of this algorithm is as follows:

let starting_time be an array of length n
current_time = 0
dfs(v):
        current_time = current_time + 1
        starting_time[v] = current_time
        shuffle children[v] randomly (each permutation with equal possibility)
        // children[v] is vector of children cities of city v
        for u in children[v]:
               dfs(u)
As told before, Barney will start his journey in the root of the tree (equivalent to call dfs(1)).
Now Barney needs to pack a backpack and so he wants to know more about his upcoming journey: for every city i, Barney wants to know the expected value of starting_time[i]. He's a friend of Jon Snow and knows nothing, that's why he asked for your help.
Input
The first line of input contains a single integer n (1 ≤ n ≤ 105) — the number of cities in USC.
The second line contains n - 1 integers p2, p3, ..., pn (1 ≤ pi < i), where pi is the number of the parent city of city number i in the tree, meaning there is a road between cities numbered pi and i in USC.
Output
In the first and only line of output print n numbers, where i-th number is the expected value of starting_time[i].
Your answer for each city will be considered correct if its absolute or relative error does not exceed 10 - 6.
Examples
input
7
1 2 1 1 4 4
output
1.0 4.0 5.0 3.5 4.5 5.0 5.0
input
12
1 1 2 2 4 4 3 3 1 10 8
output
1.0 5.0 5.5 6.5 7.5 8.0 8.0 7.0 7.5 6.5 7.5 8.0

题目翻译:
谜题
每个测试点时间限制 1
每个测试点空间限制 256兆字节
输入 标准输入
输出 标准输出
巴尼住在USC (查则合众国,United States of Charzeh). USC  n 座城市组成,编号为1  n ,由 n - 1 条道路将它们相连。城市与道路组成一颗有根树(巴尼不清楚为什么它有根)。根为1号城市。也就是说,如果一个人从1号城市出发,道路可以将他送达他想去的任何城市。
一个姑娘让巴尼爱慕,他想找到她。他从树的根开始,采用随机DFS搜索所有城市。搜索算法的伪代码如下:

starting_time 为长度为n的数组
current_time = 0
dfs(v):
        current_time = current_time + 1
        starting_time[v] = current_time
       
随机打乱 children[v] (每一种排列出现的概率相等)
        // children[v]
是城市v的子节点组成的数组
       
对于children[v]的每一个元素u
               dfs(u)
如上文所述,巴尼将从树的根出发(等价于调用dfs(1))。
现在巴尼收拾行李准备出发,他希望知道在他接下来的行程中:对于每一个城市i,巴尼想知道starting_time[i]的期望值。他是乔恩·斯诺的朋友,他什么都不懂,因此他向你求助。
输入
第一行包含一个整数 n (1 ≤ n ≤ 105) —USC的城市数量。
第二行包含 n - 1 个整数p2, p3, ..., pn (1 ≤ pi < i), pi 表示城市 i 的父节点, 也就是USC的城市 pi i 之间有一条道路相连。
输出
第一行(也是唯一一行)包含 n 个数字,第i个数字表示starting_time[i]的期望值
你的答案与标准答案的绝对或相对误差不超过10 - 6即被认为正确。
样例
输入
7
1 2 1 1 4 4
输出
1.0 4.0 5.0 3.5 4.5 5.0 5.0
输入
12
1 1 2 2 4 4 3 3 1 10 8
输出
1.0 5.0 5.5 6.5 7.5 8.0 8.0 7.0 7.5 6.5 7.5 8.0

解析
先证两个引理:
【引理1】在1~$n$的所有全排列中,数字1出现在任何一个位置的概率相等,且等于$ \frac1n$
证明:设数字1出现在了位置$ k(k\in\left\{x\in\boldsymbol N\vert1\leqslant x\leqslant n\right\})$,满足条件的全排列的个数$c_k=(n-1)!$,而全排列的个数$C=n!$,概率$ P(k)=\frac{(n-1)!}{n!}=\frac1n$
【引理2】对于$ \forall n\in\boldsymbol N\cap\lbrack2,+\infty\rbrack,\boldsymbol\;\sum_{i=1}^n\frac{\begin{pmatrix}n-1\\i-1\end{pmatrix}}{\begin{pmatrix}n\\i\end{pmatrix}}=\frac{n+1}2$
证明:设$m=n-1$,原命题转化为:$$\boldsymbol\;\sum_{i=1}^{m+1}\frac{\begin{pmatrix}m\\i-1\end{pmatrix}}{\begin{pmatrix}m+1\\i\end{pmatrix}}=\frac{m+2}2$$ $$\Leftrightarrow\boldsymbol\;\sum_{i=1}^m\frac{\begin{pmatrix}m\\i-1\end{pmatrix}}{\begin{pmatrix}m+1\\i\end{pmatrix}}+\frac{\begin{pmatrix}m\\m\end{pmatrix}}{\begin{pmatrix}m+1\\m+1\end{pmatrix}}=\frac m2+1$$ $$\Leftrightarrow\boldsymbol\;\sum_{i=1}^m\frac{\begin{pmatrix}m\\i-1\end{pmatrix}}{\begin{pmatrix}m+1\\i\end{pmatrix}}=\frac m2$$ $$\Leftrightarrow\boldsymbol\;\sum_{i=1}^m\frac{\displaystyle\frac{m!}{(m-i+1)!\cdot(i-1)!}}{\displaystyle\frac{(m+1)!}{(m+1-i)!\cdot i!}}=\frac m2$$ $$\Leftrightarrow\boldsymbol\;\sum_{i=1}^m\frac{\mathbf i}{\boldsymbol(\mathbf m\boldsymbol+\mathbf1\boldsymbol)}=\frac m2$$ $$\Leftrightarrow\frac{\displaystyle\frac{\mathbf m\boldsymbol(\mathbf m\boldsymbol+\mathbf1\boldsymbol)}{\mathbf2}}{\boldsymbol(\mathbf m\boldsymbol+\mathbf1\boldsymbol)}=\frac m2$$显然成立。故原命题成立。
引理1告诉我们,当巴尼位于点$u$时,如果点$u$的子节点有$n$个,他第$i$个遍历每一个子树的概率都等于$ \frac1n$。又根据深度优先搜索的性质,巴尼必须遍历完整棵子树后才会遍历下一棵子树,也就是一旦遍历节点$v$所在的子树,发现顺序的期望将增加$ sum_v\cdot p $$sum_v$为子树节点的总数,$p$为这种情况发生的概率)。
不妨令$n=6$我们有6种情况:第1个遍历节点$v$,第2个遍历节点$v$……第6个遍历节点$v$。为了方便讲解,设节点$u$的六个子树的节点数和分别为$a$$b$$c$$d$$e$$f$,并假设$sum_v=f$
情况一对期望的贡献为$ \frac16$,这个贡献是必须的,以下其余情况我们只讨论相对情况一的额外贡献。
情况二对期望的额外贡献为$ \frac16\cdot(\frac a5+\frac b5+\frac c5+\frac d5+\frac e5)$。可见情况二又细分为了5种子情况,假如即$a+b+c+d+e=s$,情况二的贡献可以写成$ \frac16\cdot(\frac s5)$
情况三更加复杂,因为在遍历点$v$之前,有10种遍历另外2个子树的方案,额外贡献为$ \frac16\cdot(\frac{a+b+a+c+a+d+a+e+b+c+b+d+b+e+c+d+c+e+d+e}{10})$,五个字母各出现了4次,即贡献为$ \frac16\cdot(\frac{4s}{10})$104是如何计算的呢?显然,它们分别等于$ \begin{pmatrix}5\\2\end{pmatrix}$,和$ \begin{pmatrix}4\\1\end{pmatrix}$
类似地,情况四的额外贡献为$ \frac s6\cdot\frac{\displaystyle\begin{pmatrix}4\\3\end{pmatrix}}{\begin{pmatrix}5\\4\end{pmatrix}}$
所有情况的总贡献为$\frac s6\sum_{i=1}^5\frac{\begin{pmatrix}4\\i-1\end{pmatrix}}{\begin{pmatrix}5\\i\end{pmatrix}}+1$根据引理2,这个值为$\frac s2+1$。同时,由于节点$v$会继承其父节点$u$的期望,$p_v=\frac s2+1+p_u$。而$s=sum_u-sum_v-1$,所以$ p_v=\frac{\displaystyle sum_u-sum_v-1}{\displaystyle2}+1+p_u $
于是,此题可以使用动态规划求解。

CF 718C (Sasha and Array)详解 附卡常数技巧


题目
此为题目的简体中文翻译,原文见http://codeforces.com/problemset/problem/718/C
C. 萨沙与序列
每个测试点时间限制 5
每个测试点空间限制 256 MB
输入 标准输入
输出 标准输出
萨沙有一个整数序列 a1, a2, ..., an. 你需要处理m次查询。查询有两种类型:
  1. 1 l r x — 将从lr的子序列中的所以整数的值增加x;
  2. 2 l r — 计算   \(\sum_{i=l}^rf(a_i)\) , 其中 f(x) 是第x个斐波那契数. 结果可能很大,请将其 109+7.
在本题中,斐波那契数列的定义如下: f(1) = 1f(2) = 1f(x) = f(x - 1) + f(x - 2) 其中 x > 2.
萨沙是一个聪明的孩子,他可以在5秒内处理所有查询。 你能写出与萨沙表现得一样好的程序吗?
输入
第一行包含两个整数 n  m (1 ≤ n ≤ 100 0001 ≤ m ≤ 100 000) — 分别表示序列中数字的个数与查询的次数。
下一行包含 n 个整数 a1, a2, ..., an (1 ≤ ai ≤ 109).
接下来的 m 行包含询问的描述. 每一条包含整数 tpiliri 还可能包含 xi (1 ≤ tpi ≤ 21 ≤ li ≤ ri ≤ n1 ≤ xi ≤ 109) tpi = 1 表示类型一的查询,反之  tpi 表示类型二。
保证至少存在一次类型二的查询。
输出
对于每一次类型二的查询模 109 + 7的值.
样例
输入
5 4
1 1 2 1 1
2 1 5
1 2 4 2
2 2 4
2 1 5
输出
5
7
9
说明
最初, 序列 a  11211
第一次类型二的查询的答案为 f(1) + f(1) + f(2) + f(1) + f(1) = 1 + 1 + 1 + 1 + 1 = 5
在查询 1 2 4 2 后,序列 a  13431
第二次类型二的查询的答案为  f(3) + f(4) + f(3) = 2 + 3 + 2 = 7
第三次类型二的查询的答案为  f(1) + f(3) + f(4) + f(3) + f(1) = 1 + 2 + 3 + 2 + 1 = 9
解析
为了避免歧义,这里规定f(x)指第x个斐波那契数,fi指斐波那契数列中的第i个数字,两者数值完全相等,但数学意义略有差异,为了方便均用f表示。
区间问题的一般思路为线段树,而快速计算递推式  \(f_i=f_{i-1}+f_{i-2}\)的一般思路为矩阵快速幂。
由于, \(f_i=f_{i-1}+f_{i-2}\)
有, \(\left\{\begin{array}{l}f_i=1\times f_{i-1}+1\times f_{i-2}\\f_{i-1}=1\times f_{i-1}+0\times f_{i-2}\end{array}\right.\)
即, \(\begin{bmatrix}f_i&f_{i-1}\\0&0\end{bmatrix}=\begin{bmatrix}f_{i-1}&f_{i-2}\\0&0\end{bmatrix}\cdot\begin{bmatrix}1&1\\1&0\end{bmatrix}\)

于是, \(\begin{bmatrix}f_i&f_{i-1}\\0&0\end{bmatrix}=\begin{bmatrix}f_2&f_1\\0&0\end{bmatrix}\cdot\begin{bmatrix}1&1\\1&0\end{bmatrix}^{i-2}\)                                        
类似地,可以证明当f’i为某一区间的所有数字的和时,或者更形式化地说为 \(\sum_{i=l}^rf(a_i)\) 时,①式也适用。于是,我们可以考虑线段树的叶子节点维护sumnowsumnex两个值,分别表示f(ai)f(ai+1),其他节点的sumnowsumnex分别等于左右子节点的对应值的和。更新时,令①式中的f1=sumnow, f2=sumnex,再利用矩阵快速幂即可。
矩阵快速幂的朴素实现方式如下:

#define modn %1000000007
struct matrix
{
    long long data[2][2];
    matrix operator*(const matrix &m) 
    {
        matrix m2;
        for(int i=0;i<=1;i++)
        {
            for(int j=0;j<=1;j++)
            {
                m2.data[i][j]=0;
                for(int k=0;k<=1;k++)
                {
                    m2.data[i][j]=(m2.data[i][j]+data[i][k]*m.data[k][j]modn)modn;
                }    
            }
        }    
        return m2;
    }
};
于是,我们可以写出以下朴素的程序:

#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;

#define modn %1000000007
#define mod %=1000000007

inline void read(int &x)
{
    x=0;
    char ch=getchar();
    bool flag =false;
    while(ch>'9'||ch<'0')
    {
        if(ch=='-') flag=true;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=(((x<<2)+x)<<1)+ch-48;
        ch=getchar();
    }
    if(flag) x=-x;
}

inline void read(long long &x)
{
    x=0;
    char ch=getchar();
    bool flag =false;
    while(ch>'9'||ch<'0')
    {
        if(ch=='-') flag=true;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=(((x<<2)+x)<<1)+ch-48;
        ch=getchar();
    }
    if(flag) x=-x;
}

class matrix
{
public:
    long long data[2][2];
    matrix()
    {
        memset(data,0,sizeof(data));
    }
    matrix(int opr)
    {
        if(opr==1)
        {
            data[0][0]=1;
            data[0][1]=0;
            data[1][0]=0;
            data[1][1]=1;
        }
        else if(opr==2)
        {
            data[0][0]=1;
            data[0][1]=0;
            data[1][0]=1;
            data[1][1]=0;
        }
        else if(opr==3)
        {
            data[0][0]=1;
            data[0][1]=1;
            data[1][0]=1;
            data[1][1]=0;
        }
    }
    matrix operator*(matrix s) 
    {
        matrix s2;
        for(int i=0;i<2;i++)
        {
            for(int j=0;j<2;j++)
            {
                for(int k=0;k<2;k++)
                {
                    s2.data[i][j]+=(data[i][k]*s.data[k][j])modn;
                    s2.data[i][j]mod;
                }
            }
        }
        return s2;
    }
};

matrix first(2);


matrix pow(long long p)
{
    matrix ans(1),r,s(3);
    memcpy(r.data,s.data,32);
    while(p>0)
    {
        if(p&1) ans=ans*r;
        r=r*r;
        p>>=1;
    }
    return ans;
}

struct
{
    int l,r;
    long long sumnow,sumnex;
    long long flag;
}tree[400001];

void build(int l,int r,int now=1)

{
    tree[now].l=l;
    tree[now].r=r;
    tree[now].flag=0;
    if(l==r)
    {
        long long tmp;
        read(tmp);
        tree[now].sumnow=(first*pow(tmp-1)).data[0][0];
        tree[now].sumnex=(first*pow(tmp)).data[0][0];
        return;
    }
    int mid=l+r>>1;
    build(l,mid,now<<1);
    build(mid+1,r,now<<1|1);
    tree[now].sumnex=(tree[now<<1].sumnex+tree[now<<1|1].sumnex)modn;
    tree[now].sumnow=(tree[now<<1].sumnow+tree[now<<1|1].sumnow)modn;
}

void pushdown(int now)
{
    long long flag=tree[now].flag;
    if(tree[now].flag==0) return;
    tree[now<<1].flag+=tree[now].flag;
    tree[now<<1|1].flag+=tree[now].flag;
    matrix s1,s2;
    s1.data[0][0]=tree[now<<1].sumnex;
    s1.data[0][1]=tree[now<<1].sumnow;
    s2.data[0][0]=tree[now<<1|1].sumnex;
    s2.data[0][1]=tree[now<<1|1].sumnow;
    s1=s1*pow(flag);s2=s2*pow(flag);
    tree[now<<1].sumnex=s1.data[0][0];
    tree[now<<1].sumnow=s1.data[0][1];
    tree[now<<1|1].sumnex=s2.data[0][0];
    tree[now<<1|1].sumnow=s2.data[0][1];
    tree[now].flag=0;
}

void add(int from,int to,long long x,int now=1)
{
    if(tree[now].l==from&&tree[now].r==to)
    {
        tree[now].flag+=x;
        matrix s;
        s.data[0][0]=tree[now].sumnex;
        s.data[0][1]=tree[now].sumnow;
        s=s*pow(x);
        tree[now].sumnex=s.data[0][0];
        tree[now].sumnow=s.data[0][1];
        return;
    }
    int mid=tree[now].l+tree[now].r>>1;
    pushdown(now);
    if(to<=mid)
    {
        add(from,to,x,now<<1);
    }
    else if(mid<from)
    {
        add(from,to,x,now<<1|1);
    }
    else
    {
        add(from,mid,x,now<<1);
        add(mid+1,to,x,now<<1|1);
    }
    tree[now].sumnex=(tree[now<<1].sumnex+tree[now<<1|1].sumnex)modn;
    tree[now].sumnow=(tree[now<<1].sumnow+tree[now<<1|1].sumnow)modn;
}

long long ask(int from,int to,int now=1)
{
    if(tree[now].l==from&&tree[now].r==to)
    {
        return tree[now].sumnow;
    }
    int mid=tree[now].l+tree[now].r>>1;
    pushdown(now);
    if(to<=mid)
    {
        return ask(from,to,now<<1);
    }
    else if(mid<from)
    {
        return ask(from,to,now<<1|1);
    }
    else
    {
        return (ask(from,mid,now<<1)+ask(mid+1,to,now<<1|1))modn;
    }
}

int main()
{
    int n,m;
    read(n);read(m);
    build(1,n);
    while(m--)
    {
        int opr,from,to;
        long long x;
        read(opr);
        if(opr==1)
        {
            read(from);read(to);
            read(x);
            add(from,to,x);
        }
        else
        {
            read(from);read(to);
            cout<<ask(from,to)<<endl;
        }
    }
    return 0;
}
读者不必细看这个程序,因为这是一个错误的示范。虽然时间复杂度为O(mlognloga),但大O记号中隐藏了极大的常数,于是出现了这样的惨状:

事实上,本题是可作为卡常数技巧的范例。原程序至少存在5处可优化之处。
1.      首先,最容易想到的优化是输出优化,于是输出语句被改成了:printf("%I64d\n", ask(from,to));,结果仍然是Time limit exceeded on test 11——毫无提升。
2.      观察matrix pow(long long p)函数不难发现,矩阵s实际上是恒定而多余的,于是我们事先将s矩阵计算出来,需要时直接用memcpy函数复制s.data即可。这样一来,构造函数就显得臃肿无用了。出现了以下代码(Time limit exceeded on test 20 )
matrix first,ans_,s;

matrix pow(long long p)
{
    matrix ans,r;
    memcpy(ans.data,ans_.data,32);
    memcpy(r.data,s.data,32);
    if(p<0) return ans;
    while(p)
    {
        if(p&1) ans=ans*r;
        r=r*r;
        p>>=1;
    }
    return ans;
}

int main()
{
    first.data[0][0]=1;
    first.data[0][1]=0;
    first.data[1][0]=1;
    first.data[1][1]=0;
    ans_.data[0][0]=1;
    ans_.data[0][1]=0;
    ans_.data[1][0]=0;
    ans_.data[1][1]=1;
    s.data[0][0]=1;
    s.data[0][1]=1;
    s.data[1][0]=1;
    s.data[1][1]=0;
    /*...*/
}
3.    注意到void pushdown(int now)函数可能多次重复计算矩阵\(\begin{bmatrix}1&1\\1&0\end{bmatrix}\) flag次幂,于是考虑将这个值保存在pflag中,同时void add(int from,int to,int x,int now=1)函数也存在重复计算的问题。

4.    又注意到\(\begin{bmatrix}a&c\\b&d\end{bmatrix}\cdot\begin{bmatrix}e&g\\f&h\end{bmatrix}=\begin{bmatrix}ae+cf&ag+ch\\be+df&bg+dh\end{bmatrix}\),这样矩阵乘法的函数又可以优化。甚至可以将矩阵乘法的函数“手动内联”(注:在不开启任何优化开关时,编译器不会将带有inline标识符的函数内联)。同时进行了34优化后,仍然只通过了27个测试点。
5.      进一步的优化愈来愈困难。反复观察快速幂的函数后,笔者发现:pow函数重复计算了多次\(\begin{bmatrix}1&1\\1&0\end{bmatrix}^{2^n}\) nN,与优化2的思路类似,可以事先计算出n<32时所有的 \(\begin{bmatrix}1&1\\1&0\end{bmatrix}^{2^n}\)的值,储存在数字a中。经过五次优化,程序终于通过了所有测试点。
参考程序
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;

#define modn %1000000007
#define mod %=1000000007

inline void read(int &x)
{
    x=0;
    char ch=getchar();
    bool flag =false;
    while(ch>'9'||ch<'0')
    {
        if(ch=='-') flag=true;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=(((x<<2)+x)<<1)+ch-48;
        ch=getchar();
    }
    if(flag) x=-x;
}

inline void read(long long &x)
{
    x=0;
    char ch=getchar();
    bool flag =false;
    while(ch>'9'||ch<'0')
    {
        if(ch=='-') flag=true;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=(((x<<2)+x)<<1)+ch-48;
        ch=getchar();
    }
    if(flag) x=-x;
}

//矩阵类 
class matrix
{
public:
    long long data[2][2];
    matrix operator*(const matrix &s) 
    {
        matrix s2;
        //矩阵乘法的展开,参加优化5 
        s2.data[0][0]=(data[0][0]*s.data[0][0]+data[0][1]*s.data[1][0])modn;
        s2.data[0][1]=(data[0][0]*s.data[1][0]+data[0][1]*s.data[1][1])modn;
        s2.data[1][0]=(data[1][0]*s.data[0][0]+data[1][1]*s.data[1][0])modn;
        s2.data[1][1]=(data[1][0]*s.data[1][0]+data[1][1]*s.data[1][1])modn;
        return s2;
    }
};

/*
first:
1 0
1 0
ans_(单位矩阵):
1 0
0 1
r[i]:
1 1
1 0
的i次方 
*/ 
matrix first,ans_,r[32];

//矩阵快速幂 
inline matrix pow(long long p)
{
    matrix ans;
    memcpy(ans.data,ans_.data,32);
    if(p<0) return ans;
    int i=0;
    while(p)
    {
        if(p&1) ans=ans*r[i];
        i++;
        p>>=1;
    }
    return ans;
}

//线段树的节点的结构体,各个成员的含义见解析 
struct
{
    int l,r;
    long long sumnow,sumnex;
    bool flag;
    matrix pflag;
}tree[400001];

//建树 
void build(int l,int r,int now=1)
{
    tree[now].l=l;
    tree[now].r=r;
    tree[now].flag=0;
    //由于pflag采用乘法维护,所以将其初始化为单位矩阵,相当于数量乘法中的1 
    tree[now].pflag=ans_;
    if(l==r)
    {
        long long tmp;
        read(tmp);
        matrix tmp2=first*pow(tmp);
        tree[now].sumnow=tmp2.data[0][1];
        tree[now].sumnex=tmp2.data[0][0];
        return;
    }
    int mid=l+r>>1;
    build(l,mid,now<<1);
    build(mid+1,r,now<<1|1);
    tree[now].sumnex=(tree[now<<1].sumnex+tree[now<<1|1].sumnex)modn;
    tree[now].sumnow=(tree[now<<1].sumnow+tree[now<<1|1].sumnow)modn;
}
 
void pushdown(int now)
{
    //这里的flag容易引起歧义,用flag储存tree[now].pflag只是为了写程序时方便 
    matrix flag=tree[now].pflag;
    if(!tree[now].flag) return;
    tree[now<<1].flag=1;
    tree[now<<1|1].flag=1;
    tree[now<<1].pflag=tree[now<<1].pflag*flag;
    tree[now<<1|1].pflag=tree[now<<1|1].pflag*flag;
    long long now1=tree[now<<1].sumnow,
    nex1=tree[now<<1].sumnex,now2=tree[now<<1|1].sumnow,
    nex2=tree[now<<1|1].sumnex;
    //乘法的“手动内联” 
    tree[now<<1].sumnex=(flag.data[0][0]*nex1+flag.data[1][0]*now1)modn;
    tree[now<<1].sumnow=(flag.data[1][0]*nex1+flag.data[1][1]*now1)modn;
    tree[now<<1|1].sumnex=(flag.data[0][0]*nex2+flag.data[1][0]*now2)modn;
    tree[now<<1|1].sumnow=(flag.data[1][0]*nex2+flag.data[1][1]*now2)modn;
    tree[now].flag=0;
    tree[now].pflag=ans_;
}

//更新,递归的方式与通常的写法略有差异,第一个if内的语句与pushdown类似 
void add(int from,int to,long long x,int now=1)
{
    if(tree[now].l==from&&tree[now].r==to)
    {
        tree[now].flag=true;
        matrix tmp=pow(x);
        tree[now].pflag=tree[now].pflag*tmp;
        long long nex=tree[now].sumnex,now_=tree[now].sumnow;
        tree[now].sumnex=(tmp.data[0][0]*nex+tmp.data[1][0]*now_)modn;
        tree[now].sumnow=(tmp.data[1][0]*nex+tmp.data[1][1]*now_)modn;
        return;
    }
    int mid=tree[now].l+tree[now].r>>1;
    pushdown(now);
    if(to<=mid)
    {
        add(from,to,x,now<<1);
    }
    else if(mid<from)
    {
        add(from,to,x,now<<1|1);
    }
    else
    {
        add(from,mid,x,now<<1);
        add(mid+1,to,x,now<<1|1);
    }
    tree[now].sumnex=(tree[now<<1].sumnex+tree[now<<1|1].sumnex)modn;
    tree[now].sumnow=(tree[now<<1].sumnow+tree[now<<1|1].sumnow)modn;
}

//查询区间和,为了节约时间,不模1000000007,可以计算得long long不会溢出 
long long ask(int from,int to,int now=1)
{
    if(tree[now].l==from&&tree[now].r==to)
    {
        return tree[now].sumnow;
    }
    int mid=tree[now].l+tree[now].r>>1;
    pushdown(now);
    if(to<=mid)
    {
        return ask(from,to,now<<1);
    }
    else if(mid<from)
    {
        return ask(from,to,now<<1|1);
    }
    else
    {
        return ask(from,mid,now<<1)+ask(mid+1,to,now<<1|1);
    }
}

int main()
{
    ////////////////
    //预处理 
    first.data[0][0]=1;
    first.data[0][1]=0;
    first.data[1][0]=1;
    first.data[1][1]=0;
    ans_.data[0][0]=1;
    ans_.data[0][1]=0;
    ans_.data[1][0]=0;
    ans_.data[1][1]=1;
    r[0].data[0][0]=1;
    r[0].data[0][1]=1;
    r[0].data[1][0]=1;
    r[0].data[1][1]=0;
    for(int i=1;i<32;i++)
    {
        r[i]=r[i-1]*r[i-1];
    }
    //预处理结束 
    /////////////////////// 
    int n,m;
    read(n);read(m);
    build(1,n);
    while(m--)
    {
        int opr,from,to;
        long long x;
        read(opr);
        if(opr==1)
        {
            read(from);read(to);
            read(x);
            add(from,to,x);
        }
        else
        {
            read(from);read(to);
            printf("%I64d\n",ask(from,to)modn);
        }
    }
    return 0;
}