ZOJ 3772 Calculate the Function 解题报告

原题:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=5235

题意:给定一个序列A,每次询问一组 L, R, 设 F(L) = AL, F(L+1) = AL+1, F(x) = F(x-1) + F(x-2) * Ax, 求F(R)


解法1:线段树

对于形如f(x) = p * f(x-1) + q * f(x-2)的通项都可以转成矩阵乘法,对于本题就是


于是依次拓展后得到



对于线段树的每个结点保存一个矩阵,如果结点是叶子,则有 a[0][0] = a[1][0] = 1, a[0][1] = Ax, a[1][1] = 0的形式

否则保存 a[r] * a[r-1] * ... * a[l] 的结果矩阵,要注意不要乘反了


于是对于每一个询问 l, r, 答案就是 find(l + 2, r) 的结果矩阵,再去乘 Al+1 Al 矩阵的结果


代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <string>
#include <vector>
#include <map>
#include <cmath>
using namespace std;

#define LL long long
#define ULL unsigned long long
#define mod 1000000007
#define eps 1e-8
#define MP make_pair
#define mxn 100005

LL A[mxn];
int ll[mxn << 2], rr[mxn << 2];

struct Matrix {
    LL a[2][2];
    Matrix(){}
    Matrix( int x ){
        a[0][0] = a[1][0] = 1;
        a[0][1] = x;
        a[1][1] = 0;
    }
}M[mxn << 2];

Matrix mul(Matrix A, Matrix B) {
    Matrix C;
    memset(C.a, 0, sizeof(C.a));
    for( int i = 0; i < 2; ++i )
        for( int j = 0; j < 2; ++j )
            for( int k = 0; k < 2; ++k )
                C.a[i][j] = (C.a[i][j] + A.a[i][k] * B.a[k][j]) % mod;
    return C;
}

void build( int l, int r, int i ) {
    ll[i] = l, rr[i] = r;
    if( l == r ) {
        M[i] = Matrix(A[l]);
        return ;
    }
    int m = (ll[i] + rr[i]) >> 1, ls = i << 1, rs = ls | 1;
    build(l, m, ls), build(m + 1, r, rs);
    M[i] = mul(M[rs], M[ls]);
}

Matrix find( int l, int r, int i ) {
    if( ll[i] == l && rr[i] == r ) return M[i];
    int m = (ll[i] + rr[i]) >> 1, ls = i << 1, rs = ls | 1;
    if( r <= m ) return find(l, r, ls);
    if( l  > m ) return find(l, r, rs);
    return mul(find(m + 1, r, rs), find(l, m, ls));
}

int main()
{
    //ios_base::sync_with_stdio(false);
    int t, n, m, l, r;
    cin >> t;
    while( t-- ) {
        cin >> n >> m;
        for( int i = 1; i <= n; ++i ) scanf( "%lld", A + i );
        build(1, n, 1);
        while( m-- ) {
            scanf( "%d%d", &l, &r );
            if( r - l <= 1 ) printf( "%lld\n", A[r] );
            else {
                Matrix C = find(l + 2, r, 1);
                LL ans = C.a[0][0] * A[l+1] % mod + C.a[0][1] * A[l] % mod;
                printf( "%lld\n", ans % mod );
            }
        }
    }
    return 0;
}


解法2:分块

比赛的时候智商结石了,可耻的分块了:D

假设序列从F1开始,由于F(x) = F(x-1) + F(x-2) * Ax ,可知 F1 和 F2 永远不会乘在同一个多项式内

故设 F(x) = B(x) + C(x), 其中B(x)为含有F1的多项式之和,C(x)为含有F2的多项式之和

于是我们把序列分成长为300的段,每段令F1 = F2 = 1,序列的第1个数做为F3,依次推到后面

那么对于一组确定的F1, F2, 我们有 F(x) = B(x) * F1 + C(x) * F2

当询问区间长度小于300时,可以直接撸,大于300时,先找到第一个大于l + 1的段,然后从Fl推到这个段的前面

由于我们把每段的头做为F3, 所以上一段的最后两个数,就是这一段的 F1, F2,代入即可


代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <string>
#include <vector>
#include <map>
#include <cmath>
using namespace std;

#define LL long long
#define ULL unsigned long long
#define mod 1000000007
#define eps 1e-8
#define MP make_pair
#define mxn 110005

LL A[mxn], B[mxn], C[mxn], F[mxn];

void init( int n ) {
    for( int i = 1; i <= n; i += 300 ) {
        B[i] = A[i]; C[i] = 1;
        B[i+1] = A[i]; C[i+1] = A[i+1] + 1;
        for( int j = i + 2; j < i + 300; ++j ) {
            B[j] = (B[j-1] + B[j-2] * A[j]) % mod;
            C[j] = (C[j-1] + C[j-2] * A[j]) % mod;
        }
    }
}

int main()
{
    //ios_base::sync_with_stdio(false);
    int t, n, m, l, r;
    cin >> t;
    while( t-- ) {
        cin >> n >> m;
        for( int i = 1; i <= n; ++i ) scanf( "%lld", A + i );
        init(n);
        while( m-- ) {
            scanf( "%d%d", &l, &r );
            if( r - l <= 350 ) {
                F[l] = A[l];
                F[l+1] = A[l+1];
                for( int i = l + 2; i <= r; ++i )
                    F[i] = (F[i-1] + F[i-2] * A[i]) % mod;
                printf( "%lld\n", F[r] );
                continue;
            }
            int k = 1;
            while( k <= l + 1 ) k += 300;
            F[l] = A[l]; F[l+1] = A[l+1];
            for( int i = l + 2; i < k; ++i )
                F[i] = (F[i-1] + F[i-2] * A[i]) % mod;
            for( int i = k, j; ; i = j ) {
                j = i + 300;
                if( j <= r ) {
                    F[j-2] = (B[j-2] * F[i-2] % mod + C[j-2] * F[i-1] % mod) % mod;
                    F[j-1] = (B[j-1] * F[i-2] % mod + C[j-1] * F[i-1] % mod) % mod;
                }
                else {
                    F[r] = (B[r] * F[i-2] % mod + C[r] * F[i-1] % mod) % mod;
                    printf( "%lld\n", F[r] );
                    break;
                }
            }
        }
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值