暂无链接
B君的第二题
【问题描述】
申生在内而亡,重耳在外而安
考虑k+1k+1个数组a[i](0≤i≤k)a[i](0≤i≤k)。
为了方便起见,每个数组a[i]a[i]长度为nn,下标从开始。(直观来说就是第一维下标从00开始,第二维下标从1开始。)
其中时时刻刻是a[i−1](1≤i≤k)a[i−1](1≤i≤k)的前缀和。
前缀和就是a[i][1]=a[i−1][1]a[i][1]=a[i−1][1]且a[i][j]=a[i][j−1]+a[i−1][j](j≥2)a[i][j]=a[i][j−1]+a[i−1][j](j≥2)。
比如a[0]=1,0,0,0a[0]=1,0,0,0,那么a[1]=1,1,1,1,a[2]=1,2,3,4,a[3]=1,3,6,10a[1]=1,1,1,1,a[2]=1,2,3,4,a[3]=1,3,6,10此时如果我们修改a[0][3]+=1a[0][3]+=1,得到新的a[i]a[i]。
a[0]=1,0,1,0,a[1]=1,1,2,2,a[2]=1,2,4,6,a[3]=1,3,7,13a[0]=1,0,1,0,a[1]=1,1,2,2,a[2]=1,2,4,6,a[3]=1,3,7,13。
你需要支持22个操作。
修改操作:输入,执行a[0][x]+=ya[0][x]+=y。
询问操作:输入xx,返回的值。
由于结果可能很大,你只需要输出询问的值对10000000071000000007取模的结果。
【输入格式】
第一行三个整数n,m,kn,m,k,分别表示数组长度,操作次数,前缀和次数。
接下来mm行,每行一个操作。
如果第一个数字是,接下来会有22个数字表示修改,a[0][x]+=ya[0][x]+=y。
如果第一个数字是11,接下来会有个数字xx表示询问。
【输出格式】
对于每个询问操作,输出询问的值对10000000071000000007取模的结果。
【输入样例】
4 11 3
0 1 1
0 3 1
1 1
1 2
1 3
1 4
0 3 1
1 1
1 2
1 3
1 4
【输出样例】
1
3
7
13
1
3
8
16
【数据范围】
对于100%100%的数据,满足1≤n≤100000,1≤m≤100000,1≤k≤101≤n≤100000,1≤m≤100000,1≤k≤10。
对于100%100%的数据,满足1≤x≤n,0≤y<10000000071≤x≤n,0≤y<1000000007。
对于30%30%的数据,满足1≤n,m≤10001≤n,m≤1000。
对于另40%40%的数据,满足1≤k≤21≤k≤2。
数据非常有梯度。
题解
感谢毕克不杀之恩。
终于有一道我不止会暴力的题,感觉能拿7070分,一个小时写完,跟暴力拍了几组大样例,感觉稳了啊,如果不是有个地方没取膜的话。。。
最后只有5050分,因为少取了一次膜,跪了2020分,跟暴力分+k=1k=1得分一样,mmpmmp。
虽然7070分很好得,但是要AA掉这道题,还是需要一些骚操作,考虑求前缀和四次的式子:
每当出现一个合法的i,j,k,pi,j,k,p的组合,即满足1≤p≤k≤j≤i≤x1≤p≤k≤j≤i≤x时,a[0][p]a[0][p]就会被计算一次。不等式又可以表示为1<p+1<k+2<j+3<i+4<x+51<p+1<k+2<j+3<i+4<x+5,那么一个合法的i,j,k,pi,j,k,p组合就相当于在(p+1,x+5)(p+1,x+5)中选择33个数,等于,当我们枚举pp时,就有下面的式子:
推广到kk次:
目前为止,我们就得到了一个O(nm)O(nm)的做法,还需要一波丧心病狂精妙绝伦的化简。
考虑公式如下:
组合意义是讲x+k−1−px+k−1−p分为x,k−1−px,k−1−p两部分,再枚举从xx那部分选出个数的方案,与从k−1−pk−1−p个数中选k−1−ik−1−i个数的方案相乘。
代入原式:
此时∑k−1i=0(xi)∑i=0k−1(xi)已经跟pp无关了,我们把枚举的部分放到后面去:
后面方括号框住的部分是一个前缀和的形式,而k−1−ik−1−i的取值只有kk个,所以我们直接上个数据结构维护就好了。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int M=8e5,mod=1e9+7;
int n,m,k,inv[M],base=1;
ll sum[18][M];
void in(){scanf("%d%d%d",&n,&m,&k);}
void build(){while(base<n)base<<=1;}
void add(int id,int x,int y){x+=base;for(;x;x>>=1)sum[id][x]=(sum[id][x]+y)%mod;}
int ask(int id,int ri)
{
int le=base,ans=0;ri+=base+1;
for(;le^ri^1;le>>=1,ri>>=1) {if(le&1^1)ans=(ans+sum[id][le+1])%mod;if(ri&1)ans=(ans+sum[id][ri-1])%mod;}
return ans;
}
void ac()
{
build();int op,x,y;ll ans,C;
--k;inv[1]=1;for(int i=2;i<=n+n;++i)inv[i]=1ll*inv[mod%i]*(mod-mod/i)%mod;
for(int i=1;i<=m;++i)
{
scanf("%d%d",&op,&x);
if(op){ans=0,C=1;for(int j=0;j<=k;++j)ans=(ans+C*ask(k-j,x))%mod,C=C*(x-j)%mod*inv[j+1]%mod;printf("%lld\n",ans);}
else{scanf("%d",&y);C=1;for(int j=0;j<=k;++j){add(j,x,1ll*y*C%mod);C=C*(k-x-j)%mod*inv[j+1]%mod;if(C<0)C+=mod;}}
}
}
int main(){in();ac();}

本文介绍了一种涉及多维数组前缀和计算的问题解决方法,通过巧妙利用组合数学中的组合数公式,将原始复杂度较高的算法优化至高效可行的方案。文章详细解释了如何通过化简公式达到减少计算量的目的。
7066

被折叠的 条评论
为什么被折叠?



