1. 程式人生 > >(遞推打表)牛客練習賽25 C再編號

(遞推打表)牛客練習賽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 的關係轉化式就可以了。

矩陣長這個樣子:

  sum ai
sum n-1 1
ai 0 -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);
    }
}