6466. 【GDOI2020模拟02.09】​矩阵求和

题目

题目大意不说了


思考历程

开始时感觉比赛三道题都不会做。
于是BFS式想题,
后来突然发现了一些不错的性质,一下子有了希望,认为自己能切。
可到最后才发现自己的核心部分还是没有想出来。

首先是一些简单的性质:
计算(K)次前缀和后的和,就是(K+1)次前缀和之后最后一项的和。
计算前缀和的时候可以横着先计算若干次,竖着再计算若干次。
后面都考虑一维前缀和:
前缀和若干次之后每项的系数就不说了,打打表就能出来。(亏我比赛时用生成函数分析)
显然是一个线性的多项式。
假如我们把求(K)次前缀和视为一种运算(就记作(sum_K(A))把),那满足分配律(sum_K(A+B)=sum_K(A)+sum_K(B))
这条东西是比较重要的。

对于每个行,我们都它看成一个整体。
行交换的时候,就是行的对应位置交换。
列交换的时候,就是每个行两个位置交换。
所有的权值都可以表示成(mi+j)的形式,拆开来,分别计算(mi)(j)的前缀和。对于(mi)(m)可以提出来,就是(i)的前缀和。每行的(i)都相同,对其中一行计算,出来的结果就是一个系数乘(i),这个系数在每一行都是一样的,提出来。接着再对列计算,就转成了求(i)的一维前缀和,(j)类似。
所以我们只需要用个数据结构,分别维护行、列的一维前缀和。

比赛时打到最后突然发现自己不会维护,心态崩了……


正解

现在的问题就是怎么维护这个前缀和了。
以行为例,某一行第(i)列的系数是:(C_{K+x2-i}^{K}=(K+x_2-i)^{underline{K}})
这个东西叫下降幂。
下降幂有个公式:(x^{underline{n}}=sum_{i=0}^n(-1)^{n-i}s_n^ix^i)
(s)是第一类斯特林数。
至于怎么证明就百度去吧,用归纳法是可以证的,

用这个东西来推式子,最后会搞出这个东西:
(sum_{d=0}^K(-1)^{K-d}s_K^dsum_{e=0}^d(x_2+k)^e(-i)^{d-e})
于是,维护(a_i*(-i)^k)(kin [0,10]))的和就好了。


代码

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cassert>
#define N 100100
#define ll long long
#define mo 1000000007
ll qpow(ll x,int y=mo-2){
	ll res=1;
	for (;y;y>>=1,x=x*x%mo)
		if (y&1)
			res=res*x%mo;
	return res;
}
int n,m,Q;
ll s[11][11];
int R[N],C[N];
ll pow_ik[N][11];
ll fac[N],ifac[N];
inline ll Cmn(int m,int n){return fac[m]*ifac[n]%mo*ifac[m-n]%mo;}
struct Info{
	ll s[11];
} d[2][N*4];
inline void upd(Info &c,Info &a,Info &b){
	for (int k=0;k<=10;++k)
		c.s[k]=(a.s[k]+b.s[k])%mo;
}
void init(int num,int k,int l,int r){
	if (l==r){
		for (int i=0;i<=10;++i)
			d[num][k].s[i]=pow_ik[l][i]*(num==0?C[l]:R[l])%mo;
		return;
	}
	int mid=l+r>>1;
	init(num,k<<1,l,mid);
	init(num,k<<1|1,mid+1,r);
	upd(d[num][k],d[num][k<<1],d[num][k<<1|1]);
}
void change(int num,int k,int l,int r,int x){
	if (l==r){
		for (int i=0;i<=10;++i)
			d[num][k].s[i]=pow_ik[l][i]*(num==0?C[l]:R[l])%mo;
		return;
	}
	int mid=l+r>>1;
	if (x<=mid)
		change(num,k<<1,l,mid,x);
	else
		change(num,k<<1|1,mid+1,r,x);
	upd(d[num][k],d[num][k<<1],d[num][k<<1|1]);
}
Info res;
void query(int num,int k,int l,int r,int st,int en){
	if (st<=l && r<=en){
		for (int i=0;i<=10;++i)
			(res.s[i]+=d[num][k].s[i])%=mo;
		return;
	}
	int mid=l+r>>1;
	if (st<=mid)
		query(num,k<<1,l,mid,st,en);
	if (mid<en)
		query(num,k<<1|1,mid+1,r,st,en);
}
inline ll qu(int num,int l,int r,int K){ 
	memset(&res,0,sizeof res);
	query(num,1,1,num==0?m:n,l,r);
	K--;
	ll ans=0;
	for (ll d=K,_1=1;d>=0;--d,_1*=-1){
		ll sum=0;
		for (ll e=0,x=1;e<=d;++e){
			sum+=Cmn(d,e)*x%mo*res.s[d-e]%mo;
			x=x*(K+r)%mo;
		}
		sum=sum%mo*s[K][d]%mo;
		ans=(ans+sum*_1+mo)%mo;
	}
	return ans*ifac[K]%mo;
}
int main(){
	freopen("matrix.in","r",stdin);
	freopen("matrix.out","w",stdout);
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	s[0][0]=1;
	for (int i=1;i<=10;++i){
		s[i][0]=0;
		for (int j=1;j<=i;++j)
			s[i][j]=(s[i-1][j-1]+s[i-1][j]*(i-1))%mo;
	}
	scanf("%d%d%d",&n,&m,&Q);
	int nm=max(n,m);
	for (int i=1;i<=nm;++i){
		pow_ik[i][0]=1;
		for (int j=1;j<=10;++j)
			pow_ik[i][j]=pow_ik[i][j-1]*(mo-i)%mo;
	}
	nm+=11;
	fac[0]=1;
	for (int i=1;i<=nm;++i)
		fac[i]=fac[i-1]*i%mo;
	ifac[nm]=qpow(fac[nm]);
	for (int i=nm-1;i>=0;--i)
		ifac[i]=ifac[i+1]*(i+1)%mo;
	for (int i=1;i<=n;++i)	
		R[i]=i-1;
	for (int j=1;j<=m;++j)
		C[j]=j;
	init(0,1,1,m);
	init(1,1,1,n);
	while (Q--){
		char ch[2];
		scanf("%s",ch);
		if (ch[0]=='R'){
			int x,y;
			scanf("%d%d",&x,&y);
			swap(R[x],R[y]);
			change(1,1,1,n,x);
			change(1,1,1,n,y);		
		}
		else if (ch[0]=='C'){
			int x,y;
			scanf("%d%d",&x,&y);
			swap(C[x],C[y]);
			change(0,1,1,m,x);
			change(0,1,1,m,y);
		}
		else{
			int x1,y1,x2,y2,K;
			scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&K),K++;
			ll t=qu(0,y1,y2,K)*Cmn(x2-x1+1+K-1,K)%mo;
			ll p=qu(1,x1,x2,K)*Cmn(y2-y1+1+K-1,K)%mo*m%mo;
			printf("%lld
",(t+p)%mo);
		}
	}
	return 0;
}

总结

这是一道披着数据结构的皮的计数题……
看来还是要学一些强大的计数公式。

原文地址:https://www.cnblogs.com/jz-597/p/12300461.html