2017.3.15模拟赛题解

# 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值