分块入门
题目均来自于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要对L到R这些连续的知识点提升难度X
若Choice=2,则表示小Z要提问啦
接下来读入K,表示小Z问阿M第K个知识点他已经讲到哪个难度了
输入描述
每行输出一个数表示阿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\n",ans); } } }
T2
小M的简单题(easy.pas/.cpp/.c)
时间限制:3s 内存限制:128MB
题目描述:
小M是某知名高中的学生,有一天,他请他的n个同学吃苹果,同学们排成一行,且手中已经有一些苹果。为了表示他的大方,有时他会给l到r的同学x个苹果,但为了了解分配的情况,有时他会询问l到r的同学中拥有的苹果数小于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表示给l到r的同学x个苹果,或者Q l r x表示询问l到r的同学中拥有的苹果数小于x的人的个数。(1<=l<=r<=n,0<=x<=3e4)
输出格式:
每行一个数,输出l到r的同学中拥有的苹果数小于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\n",query(x,y,z)); } } }
T3
ty的难题(ty.cpp)
时间限制: 1 Sec 内存限制: 128 MB
题目描述
题目背景:
国民男神ty又遇到了一个小难题,他在和xqj大神的争论中(谁更强),ty表示自己不会这个问题(装弱),于是他将这个问题交给了身为ty小迷弟(妹)的你。
题目描述:
给一个长为n的数列,以及n次操作。每次操作均有一个操作和3个数字组成(operate,l,r,x)。操作共有两种:
0:将l~r区间每个数加上x;
1:询问l~r区间内小于某个值x的前驱(比其小的最大元素),若不存在则输出-1。
输入
第一行:一个整数n,表示数列长为n,以及有n次操作。
第2~n+1行:每行四个数:operate,l,r,x,分别表示操作的类型、区间l~r,和加入的元素x。
输出
共n行,每行一个数,输出l到r区间内小于某个值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\n"); else printf("%lld\n",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\n",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<=10000,m<=10000,
对于100%的数据n<=100000,m<=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\n",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\n",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\n",(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\n",query(x,y,z)); } }