分块入门

题目均来自于http://hzwer.com/8053.html

评测可以在自己文件夹中分块入门里评测,小Z搞了一套题目,应该是可以的,212上有。

接下来进入主题,分块在信息学中是比较非常重要的

T1:Z的课堂检测(test)

(test.cpp/c/pas)

题目描述

大家都知道小Z的课总是十分快的(鬼知道为什么),然后我们阿M同学总是在上课时处于神游状态亦或是休眠状态,所以她对小Z到底讲了什么是一无所知。然而,小Z总是很坏地打断阿M的休眠状态,并问她问题。作为阿M的开黑好伙伴,你当然不希望阿M同学翻车(不然下一个回答问题的人就是你啦)。所以你需要编写个程序帮助阿M求小Z对于知识点到底讲的档次有多深。已知小Z在课上总会扯到涉及到N个知识点,小Z会进行M个动作(讲课或是提问阿M)。由于小Z比较古灵精怪,所以小Z的讲课时只会讲连续的知识点,并且对于这段区间内的知识点都提升一样的档次。而且,小Z也比较懒,所以小Z只会问阿M关于某一个知识点的了解程度。

输入描述

第一行读入N,表示小Z要涉及到N个知识点

第二行读入A[1],A[2]……A[N-1],A[N]表示小Z上几节课已经把第i个知识点的 难度提升到A[i]的难度

第三行读入M,表示小Z要进行M个动作

接下来M行,读入Choice

Choice=1,则表示小Z要讲课啦

接下来读入L,R,X 表示小Z要对LR这些连续的知识点提升难度X

Choice=2,则表示小Z要提问啦

接下来读入K,表示小Z问阿MK个知识点他已经讲到哪个难度了

输入描述

每行输出一个数表示阿M应该回答的正确答案

样例输入1

10

1 2 3 4 5 6 7 8 9 10

5

1 2 3 4

2 3

1 3 4 5

2 5

1 5 8 5

样例输出1

7

5

样例输入2

7

5 3 7 7 5 8 5

9

1 2 7 -1

2 1

2 2

1 2 3 1

1 2 7 2

2 2

1 3 3 -1

2 3

2 1

样例输出2

5

2

5

8

5

数据范围

对于50%的数据,N<=1000,M<=1000

对于100%的数据,N<=100000,M<=100000 |X|<=50000

|A[i]|<=50000;

压缩的题意就是给你一列数,支持区间修改和单点查询,线段树可以做,线段树用的也是分块的思想,这里用n^1/2的思想来做,是操作为O(√n)查询为O(1),和线段树一样的是,也是记录一个lazy标记,这样就可了。

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<iostream>
#include<cstring>
#include<string>

using namespace std;
const int MAXN=100007;

int n,m,blo,flag;
long long a[MAXN],bl[MAXN],a_flag[MAXN];

long long min(long long a,long long b)
{
    if (a>b) return b;
    else return a;
}
void add(int l,int r,int z)
{
    for (int i=l;i<=min(r,bl[l]*blo);i++)
        a[i]+=z;
    if (bl[l]!=bl[r])
    {
        for (int i=(bl[r]-1)*blo+1;i<=r;i++)
            a[i]+=z;
    }    
    for (int i=bl[l]+1;i<=bl[r]-1;i++)
        a_flag[i]+=z;    
}
int main()
{
//    freopen("test.in","r",stdin);
//    freopen("test.out","w",stdout);
    
    scanf("%d",&n);
    blo=(int)sqrt(n);
    for (int i=1;i<=n;i++)
    {
        scanf("%lld",&a[i]);
        bl[i]=(i-1)/blo+1;
    }
    scanf("%d",&m);
    int x,y,z;
    for (int i=1;i<=m;i++)
    {
        scanf("%d",&flag);
        if (flag==1)
        {
            scanf("%d%d%d",&x,&y,&z);
            add(x,y,z);
        }
        else 
        {
            scanf("%d",&x);
            long long ans=a[x]+a_flag[bl[x]];
            printf("%lld
",ans);
        }
    }
}

T2

M的简单题(easy.pas/.cpp/.c)

时间限制:3s 内存限制:128MB

题目描述:

M是某知名高中的学生,有一天,他请他的n个同学吃苹果,同学们排成一行,且手中已经有一些苹果。为了表示他的大方,有时他会给lr的同学x个苹果,但为了了解分配的情况,有时他会询问lr的同学中拥有的苹果数小于x的人的个数。现在,小M想让你帮他解决这道简单题。

输入格式:

第一行:两个整数n,m表示n个同学,m组询问。

第二行:n个数,a[1],a[2]...a[n]a[i]表示第i个同学一开始手中的苹果数。(0<=a[i]<=3e4)

3~m+2行:每行表示一组询问,格式为C l r x表示给lr的同学x个苹果,或者Q l r x表示询问lr的同学中拥有的苹果数小于x的人的个数。(1<=l<=r<=n,0<=x<=3e4)

输出格式:

每行一个数,输出lr的同学中拥有的苹果数小于x的人的个数。

样例输入1

5 5

1 6 3 2 3

Q 1 3 3

C 1 2 2

Q 3 4 3

C 2 3 1

Q 2 3 4

样例输出1

1

1

0

样例输入2

5 4

2 3 1 3 4

C 4 5 3

C 1 5 1

C 2 3 2

Q 1 3 4

样例输出2

1

数据范围:

 

N

M

1~3

1000

1000

4~5

30000

1000

6~10

30000

30000

压缩题意:给出一个长为n的数列,以及n个操作,操作涉及区间加法,询问区间内小于某个值x的元素个数。


可以看出这题的数据范围比上题小,因为出题人的代码也卡不过100000,而且时限开了3s,不过小Z(212)上可以1s卡过,评测机比较快吧。

说一下思路:区间加法,整个块中和打lazy标记没什么不同,但是另外的话需要重构,一次复杂度O(2√n log √n+√n);

      区间查询,整个块中用二分,不完整块内用暴力,复杂度O(√n log √n+2√n);

      简单提示:可以用vector降低代码复杂度

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#include<cstring>
#include<string>

typedef long long LL;
using namespace std;
const int MAXN=30007;

int n,m,blo,flag;
LL a[MAXN],bl[MAXN],a_flag[MAXN];
vector<LL>zhi[202];
char c[10];

void init()
{
    memset(a,0,sizeof(a));
    memset(bl,0,sizeof(bl));
    memset(a_flag,0,sizeof(a_flag));
    for (int i=0;i<=201;i++)
        zhi[i].clear();
}
LL min(LL a,LL b)
{
    if (a>b) return b;
    else return a;
}
void reset(int x)
{
    zhi[x].clear();
    for (int i=(x-1)*blo+1;i<=min(n,x*blo);i++)
        zhi[x].push_back(a[i]);
    sort(zhi[x].begin(),zhi[x].end());    
}
void add(int l,int r,int z)
{
    for (int i=l;i<=min(r,bl[l]*blo);i++)
    {
        a[i]+=z;
    }
    reset(bl[l]);
    if (bl[l]!=bl[r])
    {
        for (int i=(bl[r]-1)*blo+1;i<=r;i++)
            a[i]+=z;
        reset(bl[r]);    
    }
    for (int i=bl[l]+1;i<=bl[r]-1;i++)
        a_flag[i]+=z;
}
int query(int l,int r,int z)
{
    int res=0;
    for (int i=l;i<=min(r,bl[l]*blo);i++)
        if (a[i]+a_flag[bl[i]]<z) res++;
    if (bl[l]!=bl[r])
    {
        for (int i=(bl[r]-1)*blo+1;i<=r;i++)
            if (a[i]+a_flag[bl[i]]<z) res++;
    }    
    for (int i=bl[l]+1;i<=bl[r]-1;i++)
    {
        int x=z-a_flag[i];
        res+=lower_bound(zhi[i].begin(),zhi[i].end(),x)-zhi[i].begin();
    }
    return res;
}
int main()
{
    //freopen("easy.in","r",stdin);
    //freopen("easy.out","w",stdout);
    
    init();
    scanf("%d%d",&n,&m);
    blo=(int)sqrt(n);
    for (int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        bl[i]=(i-1)/blo+1;
        zhi[bl[i]].push_back(a[i]);
    }
    for (int i=1;i<=bl[n];i++)
        sort(zhi[i].begin(),zhi[i].end());
    int x,y,z;    
    for (int i=1;i<=m;i++)
    {
        scanf("%s",c);
        if (c[0]=='C')
        {
            scanf("%d%d%d",&x,&y,&z);
            add(x,y,z);
        }
        else 
        {
            scanf("%d%d%d",&x,&y,&z);
            printf("%d
",query(x,y,z));
        }
    }    
}

 T3

ty的难题(ty.cpp)

时间限制: 1 Sec  内存限制: 128 MB

题目描述

题目背景:

国民男神ty又遇到了一个小难题,他在和xqj大神的争论中(谁更强),ty表示自己不会这个问题(装弱),于是他将这个问题交给了身为ty小迷弟(妹)的你。

题目描述:

给一个长为n的数列,以及n次操作。每次操作均有一个操作3个数字组成(operate,l,r,x操作共有两种

0l~r区间每个数加上x

1询问l~r区间内小于某个值x的前驱(比其小的最大元素),若不存在则输出-1

输入

第一行:一个整数n,表示数列长为n,以及有n次操作。

2~n+1行:每行四个数:operate,l,r,x,分别表示操作的类型、区间l~r,和加入的元素x

输出

n行,每行一个数,输出lr区间内小于某个值x的前驱(比其小的最大元素)。

样例输入

3

1 2 3

1 1 2 2

0 1 2 1

1 1 2 2

 

样例输出

1
-1

提示

数据范围:

n<=5000

10% n<=10

30% n<=100

60% n<=1000

100% n<=5000

压缩题意:给出一个长为n的数列,以及n个操作,操作涉及区间加法,询问区间内小于某个值x的前驱(比其小的最大元素)。

题解:这道题,和上题十分类似,求小于某个值x的前驱,在每个块内,可以找块内x的前驱,怎么找比较快速呢?可以输入时存入vector里,然后sort排序,这样便可以二分查找了。块内一次查找为lg√n,其余地方暴力;修改的话,完整块里打标记,不完整块加入后重构一次O(√n log √n)。

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<cstring>
#include<string>
#include<set>

using namespace std;
const int MAXN=100007;

int n,blo;
long long a[MAXN],bl[MAXN],a_flag[MAXN];
char c[11];

set<long long>f[407];

long long max(long long a,long long b)
{
    return a>b?a:b;
}
long long min(long long a,long long b)
{
    return a>b?b:a;
}
void init()
{
    for (int i=0;i<=402;i++)
        f[i].clear();
    memset(a,0,sizeof(a));
    memset(bl,0,sizeof(bl));
    memset(a_flag,0,sizeof(a_flag));
}
void reset(int x)
{
    f[x].clear();
    for (int i=(bl[x]-1)*blo+1;i<=min(n,bl[x]*blo);i++)
        f[x].insert(a[i]);
}
void add(int l,int r,int z)
{
    for (int i=l;i<=min(r,bl[l]*blo);i++)
    {
        a[i]+=z;
    }
    reset(bl[l]);
    if (bl[l]!=bl[r])
    {
        for (int i=(bl[r]-1)*blo+1;i<=r;i++)
        {
            a[i]+=z;
        }
        reset(bl[r]);
    }
    for (int i=bl[l]+1;i<=bl[r]-1;i++)
        a_flag[i]+=z;
}
long long query(int l,int r,int z)
{
    long long ans=-MAXN;
    for (int i=l;i<=min(bl[l]*blo,r);i++)
    {
        if (a[i]+a_flag[bl[i]]<z) ans=max(ans,a[i]+a_flag[bl[i]]);
    }
    if (bl[l]!=bl[r])
    {
        for (int i=(bl[r]-1)*blo+1;i<=r;i++)
        {
            if (a[i]+a_flag[bl[i]]<z) ans=max(ans,a[i]+a_flag[bl[i]]);
        }
    }
    for (int i=bl[l]+1;i<=bl[r]-1;i++)
    {
        long long val=z-a_flag[i];
        set<long long>::iterator pos=f[i].lower_bound(val);
        //cout<<*pos<<endl;
        if (pos==f[i].begin()) continue;
        pos--;
        ans=max(ans,*pos+a_flag[i]);//pos是一个指针,所以加*为值
    }
    return ans;
}
int main()
{
    
    init();
    scanf("%d",&n);
    blo=(int)sqrt(n);
    for (int i=1;i<=n;i++)
    {
        scanf("%lld",&a[i]);
        bl[i]=(i-1)/blo+1;
        f[bl[i]].insert(a[i]);
    }
    int x,y,z;
    for (int i=1;i<=n;i++)
    {
        scanf("%s%d%d%d",c,&x,&y,&z);
        if (c[0]=='1')
        {
            long long ans=query(x,y,z);
            if (ans==-MAXN) printf("-1
");
            else printf("%lld
",ans);
        }
        else 
        {
            add(x,y,z);
        }
    }
    
}

lower_bound为大于等于的第一个值

upper_bound为大于第一个值,等于最后一个值。

T4

分块入门4

Description

给定一个长为n的序列,要求支持m次下列操作

1.区间加法,即对一段区间内的数整体加上一个数val

2.区间求和,即输出一段区间内的数的和

Input

*第一行:两个整数N,M

*第二行:N个整数,a[i]表示原数列中的第i个数

*3~m+2行:每行格式如下:ADD x y val SUM x y

Output

对于每一行询问,输出其和

Sample Input

10 5

1 2 3 4 5 6 7 8 9 10

ADD 1 5 3

SUM 2 6

ADD 4 7 2

ADD 2 6 1

SUM 1 10

Sample Output

34

83

HINT

数据范围

1<=N,M<=100000

1<=x,y<=N,-100000<=val<=100000

-100000<=A[i]<=100000

注意输入输出速度    x不一定<=y

题解:陆浩琪大神出的题,题目精简,题意明了,简单易懂。

学过线段树的朋友一定打过这道题,nlogn 十分快,而这里分块的话复杂度更高

n√n 但是谁让是分块入门练习。

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<cstring>
#include<string>

using namespace std;
typedef long long ll;
const int MAXN=100007;

int n,m,blo;
ll a[MAXN],a_flag[407],sum[407];
int bl[MAXN];
char c[11];

void init()
{
    memset(a,0,sizeof(a));
    memset(bl,0,sizeof(bl));
    memset(a_flag,0,sizeof(a_flag));
    memset(sum,0,sizeof(sum));
}
void add(int l,int r,int z)
{
    for (int i=l;i<=min(r,bl[l]*blo);i++)
    {
        a[i]+=z;
        sum[bl[i]]+=z;
    }
    if (bl[l]!=bl[r])
    {
        for (int i=(bl[r]-1)*blo+1;i<=r;i++)
        {
            a[i]+=z;
            sum[bl[i]]+=z;
        }
    }
    for (int i=bl[l]+1;i<=bl[r]-1;i++)
        a_flag[i]+=z;
}
ll query(int l,int r)
{
    ll ans=0;
    for (int i=l;i<=min(r,bl[l]*blo);i++)
        ans+=a[i]+a_flag[bl[i]];
    if (bl[l]!=bl[r])
    {
        for (int i=(bl[r]-1)*blo+1;i<=r;i++)
            ans+=a[i]+a_flag[bl[i]];
    }
    for (int i=bl[l]+1;i<=bl[r]-1;i++)
        ans+=sum[i]+a_flag[i]*blo;
    return ans;    
}
int main()
{
    init();
    scanf("%d%d",&n,&m);
    blo=(int)sqrt(n);
    for (int i=1;i<=n;i++)
    {
        scanf("%lld",&a[i]);
        bl[i]=(i-1)/blo+1;
        sum[bl[i]]+=a[i];
    }
    int x,y,z;
    for (int i=1;i<=m;i++)
    {
        scanf("%s",c);
        if (c[0]=='A')
        {
            scanf("%d%d%d",&x,&y,&z);
            if (x>y) swap(x,y);
            add(x,y,z);
        }
        else
        {
            scanf("%d%d",&x,&y);
            if (x>y) swap(x,y);
            printf("%lld
",query(x,y));
        }
    }
}

T5

田野玩游戏(game

时间限制:1Sec 内从限制:128MB

题目描述:

田野大神AK所有题感觉很水,于是开始玩起了速算游戏,屏幕上出现了n个数字。它会给出一系列操作:田野帅(0)和田野强(1)。如果是0后面有两个数l,r要求区间内所有数开,如果是1后面有两个数l,r求区间内所有数之和。

输入

第一行输入n

第二行输入n个数,

第三行输入操作数q,

后面q行操作。

输出

每个询问的答案加回车。

样例输入

10

1 2 3 4 5 6 7 8 9 10

5

0 1 10

1 1 10

1 1 5

0 5 8

1 4 8

样例输出

19

7

6

提示

对于50%的数据n<=10000m<=10000,

对于100%的数据n<=100000m<=100000,

田野说你们太菜,保证中间过程不超出longlong

压缩题意:给出一个长为n的数列,以及n个操作,操作涉及区间开方,区间求和。

题解:这题因为是不断开方,所以是log*,可以发现,一个int40亿的数开6-7次可以化为1

而0,或者1开方后不变,这是一条非常重要的性质。因此出现了一种思路,可以开始时暴力,然后如果整个块都为1或0了就标记掉,这样暴力复杂度是不高的。

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<string>
#include<cstring>

using namespace std;
const int MAXN=100007;
const int MAXN_BLO=401;

int n,m,blo;
long long a[MAXN],bl[MAXN],flag[MAXN_BLO];
long long sum[MAXN_BLO];
int con;

void init()
{
    memset(flag,0,sizeof(flag));
    memset(sum,0,sizeof(sum));
    memset(a,0,sizeof(a));
    memset(bl,0,sizeof(bl));
}
long long min(long long a,long long b)
{
    if (a>b) return b;
    else return a;
}
void solve_sqrt(int x)
{
    if (flag[x]==1) return;
    flag[x]=1;
    sum[x]=0;
    for (int i=(x-1)*blo+1;i<=min(x*blo,n);i++)
    {
        a[i]=(int)sqrt(a[i]);
        sum[x]+=a[i];
        if (a[i]>1) flag[x]=0;
    }
}
void sqrT(int l,int r)
{
    if (!flag[bl[l]]) 
    for (int i=l;i<=min(r,bl[l]*blo);i++)
    {
        sum[bl[i]]-=a[i];
        a[i]=(int)sqrt(a[i]);
        sum[bl[i]]+=a[i];
    }                     
    if (bl[l]!=bl[r])
    {
        if (!flag[bl[r]])
        for (int i=(bl[r]-1)*blo+1;i<=r;i++)
        {
            sum[bl[i]]-=a[i];
            a[i]=(int)sqrt(a[i]);
            sum[bl[i]]+=a[i];
        }
    }    
    for (int i=bl[l]+1;i<=bl[r]-1;i++)
    {
        solve_sqrt(i);
    }
}
long long query(int l,int r)
{
    long long res=0;
    for (int i=l;i<=min(r,bl[l]*blo);i++)
        res+=a[i];
    if (bl[l]!=bl[r])
    {
        for (int i=(bl[r]-1)*blo+1;i<=r;i++)
            res+=a[i];
    }    
    for (int i=bl[l]+1;i<=bl[r]-1;i++)
        res+=sum[i];
    return res;    
}
int main()
{
    freopen("game.in","r",stdin);
    freopen("game.out","w",stdout);
    
    init();
    scanf("%d",&n);
    blo=(int)sqrt(n);
    for (int i=1;i<=n;i++)
    {
        scanf("%lld",&a[i]);
        bl[i]=(i-1)/blo+1;
        sum[bl[i]]+=a[i];
    }
    int x,y;
    scanf("%d",&m);
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&con,&x,&y);
        if (x>y) swap(x,y);
        if (con==0) sqrT(x,y);
        else printf("%lld
",query(x,y));
    }
}

T6

相依为Gay

 

时间限制:1 Sec 内存限制:128MB

 

题目

    Y最近闲得慌,所以和好基友小Z玩起了一个巨无聊的游戏~~小Y有n根写有数字的签;小Y首先将自己的n根签随心所欲地排成一列,然后转身等着基友给自己出题。

   Z将会把另外的签放入两根现有的签之间(一次放一根),然后告诉小Y它的位置。有时在几个操作之间小Z会询问任意一根签上的数字,如果小Y回答不出来,嘿嘿嘿......他就要请小Z去食堂吃鸡......

Y对此表示无奈,饱经沧桑的他没有这么好的记忆力,毕竟不是所有人都像田野那么强。然而,机智的他想到了你!!!现在他正黏在你身边求你为他解决这个问题,于是善良的你不得不屈尊为他编程解决这个问题。

 

输入

第一行,一个整数n。

第二行,n个整数,表示初始序列。

3行到第3+n-1行,每行表示一个操作。若为0 a b,则在第a根签前插入写有数字b的一根签;若为1 a,小Y需回答第a根签上写有的数字。

 

输出

每行一个数,表示询问的签上的数字。

 

样例输入:

8

5 2 8 8 10 5 8 1

0 6 0

0 7 2

1 4

0 5 5

0 9 9

1 8

0 9 1

1 10

  

 

样例输出:

8

2

9  

 

数据范围

 
   

 对于50%的数据,n<=10000;对于100%的数据,n<=100000。

 

提示:分块

压缩题意:给出一个长为n的数列,以及n个操作,操作涉及单点插入,单点询问,数据随机生成。

冯哲宇的题解:

分块,分成√n块,每块内可以放一个动态的数组,每次插入时先找到位置所在的块,再暴力插入,把块内的其它元素直接向后移动一位。但是,如果先在一个块有大量单点插入,这个块的大小会大大超过√n,那块内的暴力就没有复杂度保证了。所以,我们可以——重新分块,每根号n次插入后,重新把数列平均分一下块,重构需要的复杂度为O(n),重构的次数为√n,所以重构的复杂度没有问题,而且保证了每个块的大小相对均衡。当然,也可以当某个块过大时重构,或者只把这个块分成两半。(冯哲宇)

作者题解:上面题解中解题思路忘记了重要的一点,数据是随机的,这一点还是比较重要的,以至于不会达到最坏的复杂度,次数我觉得可以改一下,本人编的代码是插入blo个数后重构一次,该浪费时还是应该浪费的,而不是有一个块到达2blo再修改。

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<iostream>
#include<cstring>
#include<string>
#include<vector>
#include<map>

using namespace std;
const int MAXN=200007;

int total,blo,n,rem;
vector<int>shu[607];
int a[MAXN],bl[MAXN],num[607];

void init()
{
    for (int i=0;i<607;i++)
        shu[i].clear();
    memset(a,0,sizeof(a));
    memset(bl,0,sizeof(bl));
    memset(num,0,sizeof(num));    
}
void reset()
{
    int nn=0;
    for (int i=1;i<=total;i++)
    {
        for (int j=0;j<shu[i].size();j++)
        {
            a[++nn]=shu[i][j];
        }
    }
    
    
    init();
    
    blo=(int)sqrt(nn);
    n=nn;    
    for (int i=1;i<=n;i++)
    {
        bl[i]=(i-1)/blo+1;
        shu[bl[i]].push_back(a[i]);
        num[bl[i]]++;
    }
    total=bl[n];
}
void add(int x,int z)
{
    int xx=0;
    for (int i=0;i<total;i++)
    {
        if (xx+num[i+1]>=x)
        {
            shu[i+1].insert(shu[i+1].begin()+x-xx-1,z);
            num[i+1]++;
            return;
        }
        else xx+=num[i+1];
    }
}
int query(int x)
{
    int xx=0;
    for (int i=0;i<total;i++)
    {
        if (xx+num[i+1]>=x) return shu[i+1][x-xx-1];
        else xx+=num[i+1];
    }
}
int main()
{
    freopen("1.in","r",stdin);
    freopen("1.out","w",stdout);
    
    scanf("%d",&n);blo=(int)sqrt(n);
    init();
    for (int i=1;i<=n;i++)
    {
        bl[i]=(i-1)/blo+1;
        scanf("%d",&a[i]);
        shu[bl[i]].push_back(a[i]);
        num[bl[i]]++;
    }
    total=bl[n];
    int flag,x,y;
    for (int i=1;i<=n;i++)
    {
        scanf("%d",&flag);
        if (flag==0)
        {
            scanf("%d%d",&x,&y);
            add(x,y);
            rem++;
        }
        else
        {
            scanf("%d",&x);
            printf("%d
",query(x));
        }
        if (rem%blo==0) reset;
    }
}

 T7

田野的武器(arms)

时间限制:1秒  空间限制:256M

题目描述

众所周知,最强者田野拥有n把史诗级武器(初始每把武器的等级都是1),他正是靠着这几把武器打下了宇宙的半壁江山。有一天,田野看着他的几把武器,感觉它们不够强,于是他就像给他们升级。

田野每次会选择连续的k把武器进行统一升t级,但由于升级的不确定性,每两次升级的级数t不一定相等。

有些时候,田野会突然得到一些翻倍卡,并且田野一旦得到翻倍卡就会使用。翻倍卡的效果是选择连续的p把武器,使它们的级数变成原来的q倍。

田野有些时候会想去打boss,他会从n把武器中选择一把,并请你告诉他这把武器的级数。

 

输入

输入第一行为两个整数n和m,表示田野有n把武器,接下来会发生m个事件。

接下来m行,每行开头会有一个字母:

S:接下来会有三个数x,y,z,表示把第x把武器到第y把武器全部升z级。

F:接下来会有三个数x,y,z,表示把第x把武器到第y把武器的等级变成原来的z倍。

B:接下来会有一个数x,表示田野想用第x把武器打boss,请你告诉他第x把武器的等级。

 

输出

对于每一个B询问各输出一行,表示那把武器的级数。

由于答案可能很大,所以答案对10^4+7取模。

 

样例输入

5 4

S 2 4 2

F 1 3 3

S 3 5 4

B 3

 

样例输出

13

 

数据范围

对于50%的数据:1≤x≤y≤n≤2000,1≤m≤1000

对于100%的数据:1≤x≤y≤n≤50000,1≤m≤20000,1≤z≤40000

压缩题意:给出一个长为n的数列,以及m个操作,操作涉及区间乘法,区间加法,单点询问。

题解:这道题,因为加法和乘法优先级不同,所以应该用两个tag标记,add表示加法标记,mul表示乘法标记。这样对于一个数有两种方法表示,a[i]=a[i]*mul+add或

a[i]=(a[i]+add)*mul;

分析这两种表示方法,

第一种:

加入一个数时,add上直接加很方便;乘入时,根据分配律(a[i]*mul+add)*z=a[i]*mul*z+add*z;

可见都是十分简便的;

第二种

加入一个数时则因该在add上加入z/mul这样会出现小数,不简便;乘法时直接修改mul标记即可。

通过对比分析,发现第一种方式更加简便,所以优先选择第一种方案。

这样题目也就可以解决了,不完整块时开始时直接重构,然后再进行乘入,即可

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<iostream>
#include<string>
#include<cstring>

using namespace std;
typedef long long LL;
const int MAXN=100007;
const int SQRT_MAXN=407;
const int MOD=10007;

int n,m,blo;
LL a[MAXN],add[SQRT_MAXN],mul[SQRT_MAXN];
int bl[MAXN];
char c[10];

void init()
{
    memset(a,0,sizeof(a));
    memset(bl,0,sizeof(bl));
    memset(add,0,sizeof(add));
}
void reset(int x)
{
    for (int i=(x-1)*blo+1;i<=x*blo;i++)
        a[i]=(a[i]*mul[x]+add[x])%MOD;
    add[x]=0;mul[x]=1;    
}
void solve(int flag,int l,int r,int z)
{
    reset(bl[l]);
    for (int i=l;i<=min(bl[l]*blo,r);i++)
    {
        if (flag==1)
            a[i]=(a[i]+z)%MOD;
        else 
            a[i]=(a[i]*z)%MOD;    
    }
    if (bl[l]!=bl[r])
    {
        reset(bl[r]);
        for (int i=(bl[r]-1)*blo+1;i<=r;i++)
        {
            if (flag==1)
                a[i]=(a[i]+z)%MOD;
            else
                a[i]=(a[i]*z)%MOD;    
        }
    }
    for (int i=bl[l]+1;i<=bl[r]-1;i++)
    {
        if (flag==1)
            add[i]=(add[i]+z)%MOD;
        else 
        {
            add[i]=(add[i]*z)%MOD;
            mul[i]=(mul[i]*z)%MOD;
        }    
    }
}
int main()
{
    freopen("arms.in","r",stdin);
    freopen("arms.out","w",stdout);
    
    init();
    
    scanf("%d%d",&n,&m);
    blo=(int)sqrt(n);
    for (int i=1;i<=n;i++)
    {
        a[i]=1;
        bl[i]=(i-1)/blo+1;
    }
    for (int i=1;i<=bl[n];i++)
        mul[i]=1;
    int x,y,z;
    for (int i=1;i<=m;i++)
    {
        scanf("%s",c);
        if (c[0]=='S')
        {
            scanf("%d%d%d",&x,&y,&z);
            solve(1,x,y,z);
        }
        else if (c[0]=='F')
        {
            scanf("%d%d%d",&x,&y,&z);
            solve(2,x,y,z);
        }
        else 
        {
            scanf("%d",&x);
            printf("%lld
",(a[x]*mul[bl[x]]+add[bl[x]])%MOD);
        }
    }
}

T8

HK练习魔法

题目描述:

HK是一个魔法学徒。这一天,他准备练习幻化魔法,这是一个很神奇的魔法,它可以让石头变成黄金(虽然是假的)。但是小A的魔力有限,而他又想要更多的练习,所以他希望你可以帮助他。

首先,在HK的面前有n个物体,他给每一个物体一个数字编号,然后他希望能够练习魔法m次,每次把从左往右数第l到r的物体变成编号为x的物体,他希望你能够计算出l到r的物体中有多少编号为x的物体,以防止他消耗不必要消耗的魔力。注意,在你计算出l到r的物体中有多少编号为x的物体,小A就会施展魔法,将l到r的物体变成编号为x的物体。

输入:

输入数据的第一行为n,代表有n个物体。第二行为n个物体的编号a[i]。第三行为m,代表有m次操作。接下来的m行均为l,r,x,代表在区间[l,r]中询问编号等于x的物体,并将这个区间的所有物体变成x。

输出:

输出有m行,表示区间[l,r]中编号等于x的物体的个数。

样例输入:

4

7 1 7 2

6

3 3 7

4 4 2

3 3 7

1 2 7

3 3 7

2 2 1

样例输出:

1

1

1

1

1

0

提示:

50%的数据n<=1000,m<=100。

100%的数据n<=100000,m<=10000。

压缩题意:给出一个长为n的数列,以及n个操作,操作涉及区间询问等于一个数c的元素,并将这个区间的所有元素改为c。

题解:这道题沈扬打的暴力居然也是比较快的,可以在4-5秒之内跑掉最快的数据,因为数据是随机生成的。回归正题,打了那么多到题,看到这题也不能说难了,整个块的话打标记,不完整的块则暴力查询即可。

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<cstring>
#include<vector>
#include<map>

using namespace std;
const int MAXN=100007;

int n,blo,m;
int a[MAXN],bl[MAXN],tag[507];

void init()
{
    memset(a,0,sizeof(a));
    memset(bl,0,sizeof(bl));
    memset(tag,-1,sizeof(tag));
}
int find(int zhi,int x)
{
    int res=0;
    for (int i=(x-1)*blo+1;i<=min(x*blo,n);i++)
    {
        if (zhi==a[i]) res++;
    }
    return res;
}
int query(int l,int r,int z)
{
    int ans=0;
    for (int i=l;i<=min(r,bl[l]*blo);i++)
    {
        if (a[i]==z&&tag[bl[i]]==-1) ans++;
        if (tag[bl[i]]==z) ans++;
        a[i]=z;
    }
    if (tag[bl[l]]!=z&&tag[bl[l]]!=-1) 
    {
        for (int i=(bl[l]-1)*blo+1;i<l;i++)
            a[i]=tag[bl[l]];
        tag[bl[l]]=-1;    
    }
    if (bl[l]!=bl[r])
    {
        for (int i=(bl[r]-1)*blo+1;i<=r;i++)
        {
            if (a[i]==z&&tag[bl[i]]==-1) ans++;
            if (tag[bl[i]]==z) ans++;
            a[i]=z;
        }    
        if (tag[bl[r]]!=z&&tag[bl[r]]!=-1) 
        {
            for (int i=r+1;i<=min(n,bl[r]*blo);i++)
                a[i]=tag[bl[r]];
            tag[bl[r]]=-1;
        }
    }
    for (int i=bl[l]+1;i<=bl[r]-1;i++)
    {
        if (tag[i]!=-1)
        {
            if (tag[i]==z)
                ans+=blo;
        }
        else ans+=find(z,i);
        tag[i]=z;
    }
    return ans;
}
int main()
{
    freopen("hk.in","r",stdin);
    freopen("hk.out","w",stdout);
    
    init();
    scanf("%d",&n);blo=(int)sqrt(n);
    for (int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        bl[i]=(i-1)/blo+1;
    }
    scanf("%d",&m);
    int x,y,z;
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        printf("%d
",query(x,y,z));
    }
}
原文地址:https://www.cnblogs.com/fengzhiyuan/p/6908322.html