(递推打表)牛客练习赛25 C再编号

链接:https://www.nowcoder.com/acm/contest/158/C
来源:牛客网
 

时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld

题目描述

n 个人,每个人有一个编号 ai 。

定义对 a 的再编号为 a' ,满足

现在有 m 次询问,每次给定 x,t ,表示询问经过 t 次再编号后第 x 个人的编号。

由于答案可能很大,所以对 109+7 取模。

输入描述:

第一行 2 个数 n,m ,表示人数和询问次数;

接下来一行 n 个数,表示 ai ;

接下来 m 行,每行 2 个数 x,t ,描述一次询问。

输出描述:

m 行,第 i 行 1 个数表示第 i 次询问的答案对 109+7 取模的结果。

 

示例1

输入

4 3
1 2 3 4
1 0
2 2
4 1

输出

1
22
6

说明

初始编号:1  2  3  4

1 次再编号后:9  8  7  6

2 次再编号后:21  22  23  24

备注:

n ≤ 100000 , m ≤ 10000 , t ≤ 100000 , 1 ≤ ai ≤ 10^9

这道题乍一看,需要对区间每一个数更新,更新的次数最大是10000。对每一个数暴力更新是肯定不行的,实际上也用不上什么数据结构能够优化操作,必须要想办法不需要每一次对每一个数更新。要么减少更新的次数,要么减少每次更新的数量。

后者当然是不可能的,所以我们必须减少更新的次数。于是我们思考,每一次更新是否有共同点,或者有什么规律可循。令sum为数列中所有数的和,经过了一次变化后,第 i 项的值 ai 变成了 sum - ai。 第二次变化的时候变成了  n * sum - sum+ai。

随后我便开窍了,变化前的表达式中 ai 这个值变化后肯定会变为 sum(并且与原来的 ai 同号) ,同时 sum 这个值会乘以n,这个很好理解因为我们必须把每一个 ai 都加起来,并且,表达式中不会出现其他的项,同时 ai 的项系数肯定是1。这个时候我想到了用矩阵快速幂,只需要知道sum 和 ai 的关系转化式就可以了。

矩阵长这个样子:

 sumai
sumn-11
ai0-1

关于矩阵的运算,稍微进行一下普及,虽然我之前写过一个关于矩阵快速幂水题的文章,但是没有介绍矩阵快速幂的原理和本质。

讲到矩阵,其实就是转化的过程。记住 现有的状态 * 矩阵=转化后的状态。就以当前我所画的这个矩阵为例,假设我们有一个向量 x=(2,1)这个向量中两个数字的含义是(sum ,ai) 也就是说 x=2*sum+ai。

那么 x 现在的状态就用这个向量来表示。那么当这个向量乘以这个矩阵的时候,就意味着 x 向量要根据矩阵当中的值来转化。矩阵的 i 行 j 列,表示了元素 i 对 元素 j 的影响因子。那么当前这个矩阵就意味着,sum(转化前)对sum(转化后)的影响因子是n-1,ai 对 sum 的影响因子为1 ai 对 ai 的影响因子是 -1 。 

那么 x = 2*sum + ai 就会变为 x = ( (n-1)*2+1*1 )*sum + (-1*1)*ai。 

普及完毕,我知道自己讲的可能很不清楚,不过之后我会单独写一篇文章讲矩阵运算。

刚刚开始的时候我们式子中只有一个 ai 所以向量为 (0,1),随后我们根据输入知道需要转化 t 次,只要乘多少次矩阵就可以了。不过矩阵是可以像普通的快速幂一样来求的。假设有矩阵 m 和向量 v ,向量 v 转化两次 可以写成 m*m*v 也可以写成 m^2*v(注意矩阵运算时从右往左算所以第一个式子先算 m*v 再乘以 m

那么给出代码。

#include<stdio.h>

const int N=100;
const long long mod=1e9+7;
typedef long long LL;


struct state{
	LL t[N][N];
};
struct vect{
	int t[N];
};

/*建立矩阵*/
void createm(state &a,int n,long long shit){
	a.t[0][0]=shit-1;
	a.t[0][1]=1;
	a.t[1][0]=0;
	a.t[1][1]=-1;
}

/*建立初始向量(0,1)*/
void init(state &a){
	a.t[0][0]=0;
	a.t[0][1]=0;
	a.t[1][0]=1;
	a.t[1][1]=0;
}

/*矩阵乘法*/
state matrixm(state a,state b,int n){
	int i,j,k;
	state temp={0};	
	for(i=0;i<n;i++)
		for(j=0;j<n;j++)
			for(k=0;k<n;k++)
				temp.t[i][j]=(temp.t[i][j]+a.t[i][k]*b.t[k][j])%mod;

	return temp;	
}

/*快速幂*/
state quick_pow(state a,int n,int len){
	int i,j;
	state temp={0};
	for(i=0;i<len;i++)
		temp.t[i][i]=1;
	while(n){
		if(n&1) temp=matrixm(a,temp,len); 
		a=matrixm(a,a,len);
		n/=2;
	}
	return temp;
}

int main(){
	state map;
	long long n,max=2,m,sum;
	int i,j,x,t;
	while(scanf("%lld%lld",&n,&m)!=EOF){
		createm(map,2,n);
		sum=0;
		for(i=0;i<n;i++){
			scanf("%lld",&a[i]);
			sum=(sum+a[i])%mod;
		}
		for(int k=0;k<m;k++){
			scanf("%d%d",&x,&t);
			state shit=quick_pow(map,t,max);
			state shit2;
			init(shit2);
			shit2=matrixm(shit,shit2,max);
			/*最后的结果应该是两项的和*/
			LL ans=(sum*shit2.t[0][0]%mod+a[x-1]*shit2.t[1][0]%mod+mod)%mod;
			printf("%lld\n",ans);
		}
	}	
	return 0;
}

不够,这个代码交上去只能过30%的样例。也就是说矩阵计算超时了。但是不用方,我们既然已经知道了递推的方法,我们就可以用别的办法来写,有时矩阵只是给我们一种递推的思维和公式。

#include <bits/stdc++.h>
using namespace std;
#define mod 1000000007
int a[100010];
long long c[100010];
int main(){
    int n,m;
    c[0]=0;
    c[1]=1;
    scanf("%d %d",&n,&m);
    for(int i=2;i<=100000;i++){
        /*打表计算每一项的sum和ai的系数*/
        if(i%2==0){
            c[i]=((n-1)*c[i-1]-1+mod)%mod;
        }
        else{
            c[i]=((n-1)*c[i-1]+1+mod)%mod;
        }
    }
    long long sum=0;
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        sum+=a[i];
    }
    sum%=mod;
    int x,y;
    while(m--){
        scanf("%d %d",&x,&y);
        if(y%2==0)
            printf("%lld\n",(sum*c[y]+a[x]+mod)%mod);
        else
            printf("%lld\n",(sum*c[y]-a[x]+mod)%mod);
    }
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值