共三道题。
教主的魔法 洛谷P2801
题目描述:有N个数,有两种操作,区间修改(加)、区间询问。
输入:第1行有两个整数n、m。第2行有n个正整数。第3行到第m + 2行,每行是一个操作,
有两种操作:
(1)第一个字母是“M”,后面三个数字L、R、W,表示对闭区间[L, R]内每个数加上W。
(2)第一个字幕是A,后面三个数字L、R、C,询问闭区间[L, R]内有多少数字大于等于C。
输出:对每个“A”询问输出一行,包含一个整数,表示大于等于C的数有多少个。
数据范围:n ≤ 1,000,000,m ≤3000,1 ≤ W≤1000,1 ≤ C≤1,000,000,000
首先暴力肯定不行 ,考虑用分块,区间修改跟板子差不多,关键是区间查询,首先考虑时间复杂度不能暴力搜每个块,进而想到二分,但不能保证每个块中的元素是递增或递减的,于是我们考虑定义一个辅助数组b[],它的初值是数列a[]的复制,排序操作在b[]上进行,然后就是一些细节要注意下
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
const int NN=1100;
int n,m,a[N],b[N],block,t,add[NN],x,y,z,op[NN],ed[NN],id[N];
char s;
int Read()
{
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch))
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(isdigit(ch))
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return f*x;
}
void reset(int p)
{
for(int i=op[p];i<=ed[p];i++) b[i]=a[i];
sort(b+op[p],b+1+ed[p]);
}
void update(int l,int r,int zhi)
{
int p=id[l];
int q=id[r];
if(p==q)
{
for(int i=l;i<=r;i++) a[i]+=zhi;
reset(p);//因为不一定覆盖整个块,所以要拖出去单独弄
return;
}
else
{
for(int i=l;i<=ed[p];i++) a[i]+=zhi;
reset(p);
for(int i=p+1;i<=q-1;i++) add[i]+=zhi;//因为整个块都被覆盖了,所以不用调顺序
for(int i=op[q];i<=r;i++) a[i]+=zhi;
reset(q);
}
}
int query(int l,int r,int k)
{
int cnt=0;
int p=id[l];
int q=id[r];
if(p==q)
{
for(int i=l;i<=r;i++) if(a[i]+add[p]>=k) cnt++;
return cnt;
}
else
{
for(int i=l;i<=ed[p];i++) if(a[i]+add[p]>=k) cnt++;
for(int i=op[q];i<=r;i++) if(a[i]+add[q]>=k) cnt++;
for(int i=p+1;i<=q-1;i++) cnt+=ed[i]-(lower_bound(b+op[i],b+ed[i]+1,k-add[i])-b)+1;
}
return cnt;
}
int main()
{
n=Read(),m=Read();
block=sqrt(n)+1;
t=n/block;
if(n%block) t++;
for(int i=1;i<=n;i++) a[i]=Read(),b[i]=a[i],id[i]=(i-1)/block+1;
for(int i=1;i<=t;i++)
{
op[i]=(i-1)*block+1;
ed[i]=i*block;
if(i==t) ed[i]=n;
sort(b+op[i],b+ed[i]+1);
}
// for(int i=1;i<=t;i++) cout<<op[i]<<" "<<ed[i]<<" ";
for(int i=1;i<=m;i++)
{
cin>>s;
x=Read(),y=Read(),z=Read();
if(s=='M') update(x,y,z);
else printf("%d\n",query(x,y,z));
}
return 0;
}
Argestes and Sequence hdu 5057
Time Limit: 5000/2500 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
题目描述:给定一个序列,有n个非负整数a[1], a[2],…, a[n]。做“单点修改 + 区间查询”操
作。
输入:第一行是整数T,表示测试用例数量。对每个测试,第一行包含两个数字n、m。第二行
是n个非负整数,用空格分割,后面有m行,每行表示一个操作,有两种操作:
S X Y: 修改操作,把a[x]的值置为y,即a[x] = y;
Q L R D P: 查询操作,询问区间[L, R]内有多少个数的第D位是P。
输出:对每个Q询问,输出一行答案。
数据范围:1≤T≤50,1≤n, m≤100000,0≤a[i]≤ 2^31-1,1≤X≤n,0≤Y≤ 2^31-1,1≤L≤R≤n,1≤D≤10,0≤P≤9
一眼过去看着像是树状数组,但仔细分析发现如果正常用树状数组数组回开到tree[10][10][100000]超空间,需要优化,所以选择用更简便的分块,分块做法就没啥说的了,直接按板子做就是了。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
const int NN=1000;
int mi[11],T,n,m,a[N],sum[NN][15][15],block,x,y,z,w,t,id[N],op[NN],ed[NN];
char s;
int Read()
{
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch))
{
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch))
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return f*x;
}
void update(int st,int zhi)
{
x=a[st];
a[st]=zhi;
for(int j=1;j<=10;j++)
{
y=x%10;
x/=10;
sum[id[st]][j][y]--;
}
for(int j=1;j<=10;j++)
{
y=zhi%10;
zhi/=10;
sum[id[st]][j][y]++;
}
}
int query(int l,int r,int u,int v)
{
int cnt=0;
int p=id[l];
int q=id[r];
if(p==q)
{
for(int i=l;i<=r;i++)
{
x=a[i];
y=x/mi[u-1];
if((y%10)==v) cnt++;
}
return cnt;
}
else
{
for(int i=l;i<=ed[p];i++)
{
x=a[i];
y=x/mi[u-1];
if((y%10)==v) cnt++;
}
for(int i=p+1;i<=q-1;i++) cnt+=sum[i][u][v];
for(int i=op[q];i<=r;i++)
{
x=a[i];
y=x/mi[u-1];
if((y%10)==v) cnt++;
}
}
return cnt;
}
int main()
{
mi[0]=1;
for(int i=1;i<=10;i++) mi[i]=mi[i-1]*10;
T=Read();
while(T--)
{
n=Read(),m=Read();
t=0;memset(sum,0,sizeof(sum));
block=sqrt(n)+1;
t=n/block;
if(n%block) t++;
for(int i=1;i<=n;i++)
{
id[i]=(i-1)/block+1;
a[i]=Read();
x=a[i];
for(int j=1;j<=10;j++)
{
y=x%10;
x/=10;
sum[id[i]][j][y]++;
}
}
for(int i=1;i<=t;i++) op[i]=(i-1)*block+1,ed[i]=i*block;
ed[t]=n;
for(int i=1;i<=m;i++)
{
cin>>s;
if(s=='S')
{
x=Read(),y=Read();
update(x,y);
}
if(s=='Q')
{
x=Read(),y=Read(),z=Read(),w=Read();
printf("%d\n",query(x,y,z,w));
}
}
}
return 0;
}
还有第三题啊,只有明天做了…
好了,来补第三题了。
luogu P3203 [HNOI2010]弹飞绵羊
题目描述:一条直线上摆着n个弹簧,每个弹簧有弹力系数,当绵羊到第i个弹簧时,它会被弹到第i+a[i]个位置,若不存在第i+a[i]个弹簧,则绵羊被弹飞。
绵羊想知道当它从第i个弹簧起步时,被弹几次后会被弹飞。为了使游戏有趣,允许修改某个弹簧的弹力。弹力系数始终为正。
仍然是分块,对每个点设两个值,一个是to[],表示该点离开自己的块第一个到的点,另一个是该点出自己块的步数,则查询就是
int query(int st)
{
int sum=0;
while(st<=n)
{
sum+=f[st];
st=to[st];
}
return sum;
}
修改和初始化就暴力就是了,由于是单点更新,并且会影响整个块其他的点的值,修改时便只用修改整个块就是了。
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+100;
const int NN=1000;
int n,a[N],to[N],block,op[NN],ed[NN],id[N],t,m,x,y,z,ans,f[N];
int Read()
{
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch))
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(isdigit(ch))
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return f*x;
}
void update(int st,int x)
{
a[st]=x;
for(int i=ed[id[st]];i>=op[id[st]];i--)
{
if(i+a[i]>=op[id[i]+1])
{
f[i]=1;
to[i]=i+a[i];
}
else
{
f[i]=f[i+a[i]]+1;
to[i]=to[i+a[i]];
}
}
}
int query(int st)
{
int sum=0;
while(st<=n)
{
sum+=f[st];
st=to[st];
}
return sum;
}
int main()
{
n=Read();
block=sqrt(n)+1;
t=n/block;
if(n%block) t++;
for(int i=1;i<=n;i++)
{
a[i]=Read();
id[i]=(i-1)/block+1;
}
for(int i=1;i<=t;i++)
{
op[i]=(i-1)*block+1;
ed[i]=i*block;
}
ed[t]=n;
for(int i=n;i>=1;i--)
{
if(i+a[i]>=op[id[i]+1])
{
f[i]=1;
to[i]=i+a[i];
}
else
{
f[i]=f[i+a[i]]+1;
to[i]=to[i+a[i]];
}
}
// for(int i=1;i<=n;i++) cout<<f[i]<<" "<<to[i]<<endl;
m=Read();
for(int i=1;i<=m;i++)
{
z=Read();
if(z==1)
{
x=Read();
x++;
printf("%d\n",query(x));
}
if(z==2)
{
x=Read();
y=Read();
x++;
update(x,y);
}
}
return 0;
}