patrick[unLOCK]
Online Judge:20191010 长沙一中 CSP模拟赛#day1
Label:好题,思维题,树状数组
题目描述
派大星的家门前有一条河(请不要向出题人提问海底为什么会有河),派大星每天要观察这条河,并且统计河中岛屿的个数。
河床的地形可以抽象为一个长度为\(n\)的数列{\(ai\)},第i位的数字代表河床对应位置的高度。
当水位为h 时,所有高度低于h 的位置都会被水覆盖,高度大于等于h 的地形就露出水面,连成了岛屿。
比如当{\(ai\)}= {\(5,3,2,6,1\)},且\(h=3\)时,河中共有2个岛屿,分别为{\(5,3\)} 和{\(6\)}。
请你协助派大星统计每一天的岛屿的个数。河床每个位置的高度可能会发生变化,而且水位也会发生变化。
输入
输入文件为patrick.in。
第一行两个正整数N;M,表示数列的长度,和询问的个数。
第二行N 个正整数Hi,表示一开始的数列,即一开始河床在每处的高度。
接下来M 行,每行有如下两种可能的格式:
- Q 外加一个整数C,表示询问若将水位抬高或降低到C^Last,湖中会有几个岛。
- C 外加两个整数A;B,表示A^Last 处的高度变为了B^Last。其中Last 表示上一次询问(Q
操作) 的答案。Last 一开始等于0,且^是按位异或的意思。
输出
输出文件为patrick.out。
共M 行,每行一个整数,代表每次询问的答案。
样例
Input#1
5 7
3 2 1 3 5
Q 1
Q 2
Q 3
C 2 2
C 5 3
C 4 2
Q 2
Output#1
1
2
1
3
Input#2
15
68 42
1 35
25 70
59 79
65 63
46 6
28 82
92 62
43 96
37 28
5 92
54 3
83 93
17 22
96 19
Output#2
1
2
1
3
询问在解码后分别为1; 3; 1; 3。最后一次询问前的数列为3; 2; 3; 2; 3。
Hint
保证通过异或之后,所有输入数据及操作数值的范围都在\([1,500000]\)之内。
测试点信息
对于测试点1/2/3,\(N,M<=5000\);
对于测试点4/5/6,\(N,M<=5*10^5\),保证没有C操作;
对于测试点7/8,\(N,M<=10^5\);
对于测试点9/10,\(N,M<=5*10^5\)。
题解
Substack1
每个询问暴力\(O(N)\)统计一遍,时间复杂度\(O(N\cdot M)\),可以通过测试点1/2/3。
Substack2
算法一
没有C操作。直接预处理出\(ans[1..maxn]\),对于每个询问\(O(1)\)回答。
按河床的高度,从高到低添加,利用打标记的方式维护当前岛屿数。
时间复杂度为\(O(N)\)。结合Substack1可以通过前6个测试点。
代码如下:
#include<bits/stdc++.h>
using namespace std;
bool nc1;
inline int read(){
int x=0;char c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x;
}
const int N=5e5+10;
int a[N],n,m;
namespace p30{
inline int Ask(int h){
int num=0;
a[0]=a[n+1]=-1;
for(register int i=1;i<=n+1;++i)if(a[i-1]>=h&&a[i]<h)++num;
return num;
}
void solve(){
int lst=0;
while(m--){
char op[3];scanf("%s",op);
if(op[0]=='Q'){
int x=read();
lst=Ask(x^lst);
printf("%d\n",lst);
}
else{
int A=read(),B=read();
A^=lst,B^=lst;
a[A]=B;
}
}
}
}
namespace p60{
int mx=0,id[N],ans[N];
bool mark[N],boom[N];
inline bool cmp(int p,int q){return a[p]>a[q];}
void solve(){
for(register int i=1;i<=n;++i)id[i]=i,mx=max(mx,a[i]);
sort(id+1,id+n+1,cmp);
int num=0;
for(register int i=1;i<=n;++i){
int x=id[i],nowh=a[x];
mark[x]=1;
if(mark[x-1]&&mark[x+1])num--;
else if(!mark[x-1]&&!mark[x+1])num++;
ans[nowh]=num;boom[nowh]=1;
}
for(register int i=mx;i>=0;--i)if(!boom[i]){
ans[i]=ans[i+1];
}
int lst=0;
while(m--){
char op[3];scanf("%s",op);
if(op[0]=='Q'){
int x=read();x^=lst;
lst=ans[x];
printf("%d\n",lst);
}
}
}
}
bool nc2;
int main(){
// cout<<(&nc2-&nc1)/1024/1024<<endl;
// freopen("patrick.in","r",stdin);
// freopen("patrick.out","w",stdout);
n=read(),m=read();
for(register int i=1;i<=n;++i)a[i]=read();
if(n<=5000&&m<=5000){p30::solve();return 0;}
p60::solve();
}
算法二
其实是为后面的100pts做法准备的。
算法一是通过,逆着一个个加块的方式来求的,有没有什么方法能够一遍扫过去,求得所有水位下的对应答案呢?
通过yy可以想到下面的做法:对于当前河床\(x\),只考虑\(x\)和\(x+1\)的相对关系,假如存在\(a[x]>a[x+1]\),我就将\([a[x+1]+1,a[x]]\)这个区间内的水位会形成的岛屿数+1。因为在\(x,x+1\)这里形成了一个断层,x往前一段都会在水位为\([a[x+1]+1,a[x]]\)时形成一块岛屿。
区间加可以用差分完成,统计时利用前缀和。时间复杂度为\(O(N)\)。
Substack3
根据算法二不难想到正解,只要利用树状数组修改即可(区间修改,单点查询)。当更改\(x\)的高度时,先回撤{\(x-1,x\)},{\(x,x+1\)}对答案的影响,再给\(a[x]\)赋上新的值,重新计算一遍{\(x-1,x\)},{\(x,x+1\)}对答案的影响。
综上时间复杂度为\(O(NlogN)\)。
完整代码如下:
#include<bits/stdc++.h>
#define N 500010
using namespace std;
inline int read(){
int x=0;char c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x;
}
int a[N],n,m;
struct Bit{
int c[N];
void add(int x,int d){while(x<N)c[x]+=d,x+=x&-x;}
int ask(int x){
int res=0;
while(x)res+=c[x],x-=x&-x;
return res;
}
void update(int l,int r,int d){add(l,d),add(r+1,-d);}
}B;
void BOOM(int x,int d){
if(x!=1&&a[x-1]>a[x])B.update(a[x]+1,a[x-1],d);
if(a[x]>a[x+1])B.update(a[x+1]+1,a[x],d);
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=n;i++)if(a[i]>a[i+1])B.update(a[i+1]+1,a[i],1);
int lst=0;
while(m--){
char op[3];scanf("%s",op);
if(op[0]=='Q'){
int x=read()^lst;
printf("%d\n",lst=B.ask(x));
}
else{
int x=read()^lst,d=read()^lst;
BOOM(x,-1);a[x]=d;BOOM(x,1);
}
}
}