[cf1599f]Mars

题意:给一个模1e9+7下的长度为n的序列,有q个询问,问对于区间[Li, Ri],是否能将其排列成一个模1e9+7意义下的,公差为Di的等差数列?

对于这个验证方法有些很奇妙的思路……也许是套路吧,我太菜了,不太熟。

我们可以求得原序列的前缀和,得到Si = sum(Li...Ri)。令len = Ri - Li + 1, 假如说我们能构造出一个等差数列,那么应该有Si = len * A0 + len * (len - 1) / 2 * Di。所以我们可以用Si和Di算出一个A0,那么这个等差数列的通项公式就是Ai = A0 + (i - 1) * Di   mod 1e9+7。

等差数列和子序列[Li, Ri]完全相等的一个必要条件是,两序列中每个元素k次方的和相等,k取任意值。我们选择两个合适k进行验证,基本就可以得到正确的结果。

原序列子序列的k次方部分和可以预处理。等差数列的一端则需要推一推式子。我们上面已经计算出A0,如果A0 = 0,答案则为{D_i}^k*\Sigma_{i=0}^{n-1}i^k; 如果A0 ≠ 0,令x = \frac{D_i}{A_0}  mod 1e9+7, 答案可化为{A_0}^k * \Sigma_{i=0}^k(_i^k)*b(i,n)*x^i, 其中b(i,n)= \Sigma_{j=0}^{n-1}j^i, 特别地,0^0=1。可以看到第一种情况也用到我们第二种情况的b数组。b数组可以预处理,其和组合数的乘积(即情况2中多项式的系数)也可以预处理,复杂度都是O(N*k)。总复杂度O((N+Q)*k)。

题解给的k参考值是51和52,我这里设的比较小,但倒是也过了。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int N=200005,M=60;
const int mod=1e9+7;
int n,q,hf,k1,k2,cur1,cur2,tar1,tar2,a1[N],c1[M][M],s1[M][N],sum[N],mul[2][N],b1[2][N][M];
inline int read(){
	int x=0;char ch=getchar();
	while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
	return x;
}
inline int qpow(int x,int y){
	ll s=1,t=1ll*x;
	while(y){
		if(y&1)s=s*t%mod;
		t=t*t%mod;
		y>>=1;
	}
	return s;
} 
int main(){
	n=read(),q=read();
	k1=13,k2=18;
	hf=qpow(2,mod-2);
	mul[0][0]=mul[1][0]=sum[0]=0;
	for(int i=1;i<=n;++i){
		a1[i]=read();
		sum[i]=(sum[i-1]+a1[i])%mod;
		mul[0][i]=(mul[0][i-1]+qpow(a1[i],k1))%mod;
		mul[1][i]=(mul[1][i-1]+qpow(a1[i],k2))%mod;
	}
	c1[0][0]=1,s1[0][1]=1;
	for(int i=2;i<=n;++i)s1[0][i]=s1[0][i-1]+1;
	for(int i=1;i<=k2;++i){
		c1[i][0]=1;
		for(int j=1;j<=i;++j)c1[i][j]=(c1[i-1][j-1]+c1[i-1][j])%mod;
		s1[i][1]=0;
		for(int j=2;j<=n;++j)s1[i][j]=(s1[i][j-1]+qpow(j-1,i))%mod;
	}
	for(int i=1;i<=n;++i){
		for(int j=0;j<=k1;++j)
			b1[0][i][j]=1ll*s1[j][i]*c1[k1][j]%mod;
		for(int j=0;j<=k2;++j)
			b1[1][i][j]=1ll*s1[j][i]*c1[k2][j]%mod;
	}
	for(int i=1,l,r,d,len,a0,x;i<=q;++i){
		l=read(),r=read(),d=read();
		if(l==r){printf("Yes\n");continue;} 
		if(l>r){printf("No\n");continue;}
		cur1=(mul[0][r]-mul[0][l-1]+mod)%mod;
		cur2=(mul[1][r]-mul[1][l-1]+mod)%mod;
		len=r-l+1;
		a0=(1ll*sum[r]-1ll*sum[l-1]-1ll*len*(len-1)%mod*hf%mod*d%mod+2ll*mod)%mod*qpow(len,mod-2)%mod;
		if(a0){
			x=1ll*d*qpow(a0,mod-2)%mod;
			tar1=tar2=0;
			for(int j=0;j<=k1;++j)
				tar1=(tar1+1ll*qpow(x,j)*b1[0][len][j]%mod)%mod;
			for(int j=0;j<=k2;++j)
				tar2=(tar2+1ll*qpow(x,j)*b1[1][len][j]%mod)%mod;
			tar1=1ll*tar1*qpow(a0,k1)%mod;
			tar2=1ll*tar2*qpow(a0,k2)%mod;
		}else{
			tar1=1ll*qpow(d,k1)*s1[k1][len]%mod;
			tar2=1ll*qpow(d,k2)*s1[k2][len]%mod;
		}
		if(cur1==tar1&&cur2==tar2)printf("Yes\n");
		else printf("No\n");
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值