# 3.11小题解
–by cym
## T1 前缀?(a.cpp/c/pas)
本题是一道非常水的DP由于n只有400;O(n^3^)轻松水过
(而且只是渐进意义,远远达不到n^3^)。
30%分数
直接开n个for枚举,强行判断。
50%分数
dfs的方式枚举,会比强行暴力快。
80%分数
80%的作法已经是DP了。我们开个数组f[i][a1][a2]记录到第i位时,有a1个2 与 a2个1;
0不用存的原因是i-a1-a2就是0的个数。
然后我们就可以开始DP:
1、先特判f[1][1][0]=1;显然只有一位的时候只能是一位的2。
2、我们的第一层for循环从2开始到n,这是枚举有几位。
3、第二层for循环是枚举a1到i,由于2是最多的条件,我们显然不能从1开始,必须从n/2向上取整的位置开始。
3、第三层for循环是枚举a2到i-a1但是由于1要比2数量少,a1<a2也是条件之一。
然后1要比0多,我们的a2不能从1开始,要从(i-a1)/2向上取整开始。
最后在for循环里我们判断如果少掉一个2是满足条件的就加上f[i-1][a1-1][a2]的数量。
同理如果少掉一个1是满足条件的就加上f[i-1][a1][a2-1]的数量。
少掉一个0是满足条件的就加上f[i-1][a1][a2]的数量。
统计答案时,要将i=n时的所有情况全加上,来两层for枚举a1与a2将f加上。
在所有取和的操作时都别忘了取模。
但这样的空间不够用只能拿80%。
100%分数
100%分数的作法其实就是80%分数作法的改进。
我们发现第一维只有用到上一层的情况我们就可以愉快的滚存了。空间于是达到要求。
PS:太水就不贴代码了
T2 前缀!(b.cpp/c/pas)
这道题稍微有点技巧性了。我们需要求前缀和的前缀和的前缀和……当然是若干个。
观察到本题的数据范围均<=4000我们容易联想到O(n^2^)的算法,事实上就是如此。
100%分数
参照四个1的表格。(忘记怎么用markdown的表了)。直接来手码:
add前 || add后
------------------------------------
f0| 1 | 1 | 1 | 1 || 2 | 1 | 1 | 1 |
f1| 1 | 2 | 3 | 4 || 2 | 3 | 4 | 5 |
f2| 1 | 3 | 6 | 10|| 2 | 5 | 9 | 14|
f3| 1 | 4 | 10| 20|| 2 | 7 | 16| 30|
f4| 1 | 5 | 15| 35|| 2 | 9 | 25| 55|
只看add前部分我们可以发现这实际上就是第i位数对第j层第k个数所产生的贡献我们可以事先统计出这个表
之后直接求第k层的第x个数时,我们直接将1到x数的贡献乘上数字的大小之和就是目前所求的答案。
当然实际上的表应该倒序存,给出表的层数是打出表层数减1。如果k=0的话直接输出a[k]。
add操作直接在a数组上进行。
我们就完成了这道题。
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#define ll long long
using namespace std;
inline ll read(){
ll x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
inline void wri(ll x){
if(x>=10) wri(x/10);
putchar(x%10+'0');
}
void write(ll x){
if(x<0){putchar('-'); x=-x;}
wri(x);
}
void writeln(ll x){
write(x);
puts("");
}
const int N=4010;
const ll mod=1000000007;
int n,m,q;
ll a[N],p[N][N];
int main(){
freopen("b.in","r",stdin);
freopen("b.out","w",stdout);
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=n;++i)
a[i]=read();
for(int i=0;i<n;++i)
p[1][i]=1;
for(int i=2;i<=m;++i)
for(int j=0;j<n;++j)
if(j!=0)
p[i][j]=(p[i-1][j]+p[i][j-1])%mod;
else p[i][j]=p[i-1][j];
char ch[10];
ll x,y,ans;
while(q--){
scanf("%s",ch);
x=read(); y=read();
if(ch[0]=='Q'){
if(x==0) writeln(a[y]);
else{
ans=0;
for(int i=1;i<=y;i++)
ans=(ans+p[x][y-i]*a[i])%mod;
writeln(ans);
}
}
else a[x]=(a[x]+y)%mod;
}
return 0;
}
T3前缀……(c.cpp/c/pas)
此题非常毒瘤,原为出题正好研究该类型的题目,顺便就处给我们做了……
题目内容和T2一样,不过它的数据范围发生了改变。
m<=10;n,q<=100000
嗯,非常的毒瘤我们的第二题的作法就不能用了。
首先推测时间复杂度大概为O(n m log n);
观察到m的数据范围很小,有前缀和的关系,应该会用到数据结构,猜测为树状数组。(事实说明是正确的)
这里需要维护十重前缀和,就容易想到十重树状数组。
这里我直接沿用学长本身的题解。
以前做过一题求前缀和的前缀和,我从那题受到启发想到了这题,但应该有比我的方
法更好的方法。
对于 15%的数据,一棵树状数组动态维护前缀和。
对于 40%的数据,两棵树状数组:一棵维护前缀和,一棵维护
Ai*(n-i+1)的和,答案
就是 Tree2[i]-Tree1[i]*(n-i)。
对于 60%和 80%的数据,其实是差一点就想到标算了,方法和标算类似,这里不再赘述。
考虑满分算法,由 15%和 40%的方法可以猜想,维护前缀和用一棵树状数组,维护前缀
和的前缀和用两棵树状数组,那维护 10 重前缀和,是不是用 10 棵树状数组呢?
如果真的
是用 10 棵树状数组,那维护什么呢?这时我们很容易想到 Ai*(n-i+1)中的(n-i+1),这个
数的意义是什么呢?它表示第 i 个数对第二重前缀和的最后一位的贡献(即加了几次)。
我们假设前四个数分别为 a、b、c、d,要维护的是三重前缀和。
有没有看出什么规律来呢?如果用 num[j][i]表示第 j 层(即第 j 重前缀和)时,Ai
对第 j 层的最后一位的贡献的话,num[j][i]=num[j][i+1]+num[j-1][i]
我们就可以很容易地求出每个数的贡献,O(n * 0)预处理出来。这样思路就明朗了:第 j 棵树状数组维护
Ai * num[j][i]的和
知道了怎么维护,接下来就是想怎么求了。我们用 Ans[j][i]表示第 j 重前缀和的第 i
个数(即答案)。同样,我们来思考 Tree2[i]-Tree1[i]*(n-i)中(n-i)的意义,它表示前
i 个数多算的贡献,我们能否也像 num[j][i]一样打出一张表 f[j][i]呢?
我们以上表为例,
进一步去寻找规律。
我们把上表的字母去掉,只剩下系数。
发现 Ans[j][i]的系数是 Ans[j][i+1]的系数的后缀。
Ans[3][2](3 1 0 0)有一种求法:
(10 6 3 1)-(4 3 2 1)=(6 3 1 0)
(6 3 1 0)-(3 2 1 0)=(3 1 0 0)
考虑到对于 Ans[j][i]来说,若 k>i,则 Ak 对 Ans[j][i]的贡献必然为 0。这样的话,
Ans[j][i]的值就只与 Tree[k]i有关了。
由于上表太小找不到规律,我再画大一点。求 Ans[5][2](5 1 0 0 0),即(5 1)。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#define ll long long
#define lowbit(x) (x&(-x))
using namespace std;
inline ll readll(){
ll x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
inline void write(int x){
if(x<0){putchar('-'); x=-x;}
if(x>=10) write(x/10);
putchar(x%10+'0');
}
void writeln(int x){
write(x);
puts("");
}
const int N=100100;
const int mod=1000000007;
int ans;
int n,m,q,ch,x,y;
int f[11][N],c[11][N],num[11][N];
int a[N];
char s[10];
void add(int k,int x,int y){
for( ;x<=n;x+=lowbit(x))
c[k][x]=(c[k][x]+y)%mod;
}
int query(int k,int x){
int t=0;
for( ;x;x-=lowbit(x))
t=(c[k][x]+t)%mod;
return t;
}
int main(){
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);
n=read(); m=read(); q=read();
for(int i=1;i<=n;i++)
f[1][i]=i;
for(int i=2;i<=10;i++)
for(int j=1;j<=n;j++)
f[i][j]=(f[i-1][j]+f[i][j-1])%mod;
for(int i=1;i<=n;i++)
num[1][i]=1;
for(int i=2;i<=10;i++)
for(int j=n;j;j--)
num[i][j]=(num[i-1][j]+num[i][j+1])%mod;
for(int i=1;i<=n;i++){
a[i]=read();
for(int j=1;j<=10;j++)
add(j,i,1ll*a[i]*num[j][i]%mod);
}
while(q--){
scanf("%s",s);
if(s[0]=='Q'){
y=read(); x=read();
if(!y) writeln(a[x]);
else{
ans=query(y,x);
for(int j=n-x,k=1;k<y&&j;j--,k++){
if(k&1)
ans=(ans+mod-1ll*f[k][j]*query(y-k,x)%mod)%mod;
else
ans=(ans+1ll*f[k][j]*query(y-k,x)%mod)%mod;
}
writeln(ans);
}
}
else{
x=read(); y=read();
a[x]=(a[x]+y)%mod;
for(int i=1;i<=10;i++)
add(i,x,1ll*y*num[i][x]%mod);
}
}
return 0;
}