2017.3.15模拟赛题解

来源:互联网 发布:定时开关机软件下载 编辑:程序博客网 时间:2024/05/17 03:03

# 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 longusing 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
原创粉丝点击