7.22NOIP模拟赛
终于场切了一道
A
中文题面

开两个线段树乱搞
如果一个数是区间中每一个数的约数,那么它一定是整个区间中的最小值(不唯一)
所以推出它是整个区间中所有数的最大公约数(gcd)
所以只要开线段树维护区间最小值和最小值的数量,区间gcd
每次判断该区间的最小值是否等于其gcd,若相等,输出最小值的数量,否则输出0
#include<bits/stdc++.h>
#define fst first
#define sec second
#define usetime() (double)clock () / CLOCKS_PER_SEC * 1000.0
using namespace std;
typedef pair<int,int> auther;
const int maxn=5e5+5;
const int inf=2e9+1;
void read(int& x){
char c;
bool f=0;
while((c=getchar())<48) f|=(c==45);
x=c-48;
while((c=getchar())>47) x=x*10+c-48;
x=(f ? -x : x);
return;
}
int mini(int x,int y){
return x<y ? x : y;
}
int t1[maxn<<2],t2[maxn<<2];//t1区间最小值,t2区间gcd
int p[maxn<<2];//p区间最小值的个数
int a[maxn];
int n,q;
//线段树1,维护区间最小值和其数量
void push_up1(int u){
t1[u]=mini(t1[u<<1],t1[u<<1|1]),p[u]=0;
if(t1[u]==t1[u<<1]) p[u]+=p[u<<1];
if(t1[u]==t1[u<<1|1]) p[u]+=p[u<<1|1];
}
void build1(int l=1,int r=n,int u=1){
if(l==r){
t1[u]=a[l];
p[u]=1;
return;
}
int mid=(l+r)>>1;
build1(l,mid,u<<1),build1(mid+1,r,u<<1|1);
push_up1(u);
}
void update1(int x,int p,int l=1,int r=n,int u=1){
if(r<x||l>x) return;
if(l==r){
t1[u]=p;
return;
}
int mid=(l+r)>>1;
update1(x,p,l,mid,u<<1),update1(x,p,mid+1,r,u<<1|1);
push_up1(u);
}
auther query1(int L,int R,int l=1,int r=n,int u=1){
if(r<L||l>R) return make_pair(inf,0);
if(r<=R&&l>=L) return make_pair(t1[u],p[u]);
int mid=(l+r)>>1;
auther i=query1(L,R,l,mid,u<<1),j=query1(L,R,mid+1,r,u<<1|1);
if(i.fst<j.fst) return i;
else if(i.fst>j.fst) return j;
else return make_pair(i.fst,i.sec+j.sec);
}
//线段树2,维护区间gcd
void push_up2(int u){
t2[u]=__gcd(t2[u<<1],t2[u<<1|1]);
}
void build2(int l=1,int r=n,int u=1){
if(l==r){
t2[u]=a[l];
return;
}
int mid=(l+r)>>1;
build2(l,mid,u<<1),build2(mid+1,r,u<<1|1);
push_up2(u);
}
void update2(int x,int p,int l=1,int r=n,int u=1){
if(r<x||l>x) return;
if(l==r){
t2[u]=p;
return;
}
int mid=(l+r)>>1;
update2(x,p,l,mid,u<<1),update2(x,p,mid+1,r,u<<1|1);
push_up2(u);
}
int query2(int L,int R,int l=1,int r=n,int u=1){
if(r<L||l>R) return -1;
if(r<=R&&l>=L) return t2[u];
int mid=(l+r)>>1;
int ans;
int bl=query2(L,R,l,mid,u<<1),br=query2(L,R,mid+1,r,u<<1|1);
if(bl<0&&br<0) ans=-1;
else if(bl<0) ans=br;
else if(br<0) ans=bl;
else if(bl>0&&br>0) ans=__gcd(bl,br);
return ans;
}
int main(){
//freopen("multiples.in","r",stdin);
//freopen("multiples.out","w",stdout);
read(n),read(q);
for(int i=1;i<=n;i++) read(a[i]);
build1(),build2();
int op,l,r;
while(q--){
read(op),read(l),read(r);
if(op==1){
update1(l,r),update2(l,r);
}
else{
auther j=query1(l,r);
int k=query2(l,r);
if(j.fst==k){
printf("%d\n",j.sec);
}
else{
printf("0\n");
}
}
}
return 0;
}
B
原题在这里
中文题面

考虑计算一个数字为结尾时的数量
若一个数字是结尾,则它一定是合法子串中的最大值,即不可能包含其他比它大的数字
所以可以处理出它左边第一个比它大的数,记为f[i]
那么开头怎么选取呢?一定是比它小的数,且区间内不可能有比它还小的数
前面处理左边第一个比它大的数用到了单调栈,这里的情况刚好也可以用单调栈解决
我们再维护一个单调递增(非严格)的栈,每有一个数插入,都要弹出栈顶比它大的所有数字
这也对应了若有一个较小的数排在较大的数前面,后面较大的数是不能作为开头的,因为区间中有比它小的数
(此处较小较大形容数的相对大小关系)
因为不能作为开头的数一定在插入的过程中被弹掉了
所以我们要选的开头一定在当前的单调栈中,并且编号比f[i]大
由于这里要用upper_bound寻找边界,所以手动模拟栈的实现
#include<bits/stdc++.h>
#define usetime() (double)clock () / CLOCKS_PER_SEC * 1000.0
using namespace std;
typedef long long LL;
const int maxn=1e6+5;
void read(int& x){
char c;
bool f=0;
while((c=getchar())<48) f|=(c==45);
x=c-48;
while((c=getchar())>47) x=x*10+c-48;
x=(f ? -x : x);
return;
}
int st[maxn];//手动模拟栈实现
int a[maxn],f[maxn];
int tp=0;//栈顶
int n;
int main(){
read(n);
for(int i=1;i<=n;i++) read(a[i]);
for(int i=1;i<=n;i++){
while(tp&&a[st[tp]]<=a[i]) --tp;//维护一个严格单调递减的栈
f[i]=st[tp];
st[++tp]=i;
}//计算每一个数左边第一个比它大的数
tp=0;//清空栈
LL ans=0;
for(int i=1;i<=n;i++){
while(tp&&a[st[tp]]>a[i]) --tp;
st[++tp]=i;//维护一个非严格单调递增的栈
int p=upper_bound(st+1,st+tp+1,f[i])-st;//限制查询范围
//for(int i=p;i<=tp;i++) cout<<a[st[i]]<<' '<<a[i]<<endl;
//这段代码可以输出每个合法区间
ans+=tp-p+1;
}
printf("%lld",ans);
return 0;
}
C
中文题面

转化问题
原问题等价于判断一个矩形中是否满足所有行中均有1或所有列中均有1
考虑离线+扫描线
例如扫行时,排序并遍历每个矩形的下边界,在扫到一个边界前预处理出这个边界前的所有点
在线段树上维护这些点的行坐标(每个点放入其列坐标的桶中),到一个边界时,看区域内的最小的行坐标
如果小于该矩形的上边界,则说明这个矩形有一些列上是没有点的
否则说明这个矩形的所有列上都有点,那么该询问的答案为YES
同理扫列,这里可以通过交换点和矩形的行列坐标再调用原先扫行的函数解决
#include<bits/stdc++.h>
#define fst first
#define sec second
#define usetime() (double)clock () / CLOCKS_PER_SEC * 1000.0
using namespace std;
typedef pair<int,int> auther;
const int maxn=1e5+5;
const int inf=2e9+1;
void read(int& x){
char c;
bool f=0;
while((c=getchar())<48) f|=(c==45);
x=c-48;
while((c=getchar())>47) x=x*10+c-48;
x=(f ? -x : x);
return;
}
struct sq{
int x1,y1,x2,y2,id;
};
bool cmp1(auther a,auther b){
return a.fst<b.fst;
}
bool cmp2(sq a,sq b){
return a.x2<b.x2;
}
int n,m,k,q;
int mx;
auther d[maxn];
sq s[maxn];
int t[maxn<<2];
void push_up(int u){
t[u]=min(t[u<<1],t[u<<1|1]);
}
void update(int x,int p,int l=1,int r=mx,int u=1){
if(r<x||l>x) return;
if(l==r){
t[u]=p;
return;
}
int mid=(l+r)>>1;
update(x,p,l,mid,u<<1),update(x,p,mid+1,r,u<<1|1);
push_up(u);
}
int query(int L,int R,int l=1,int r=mx,int u=1){
if(r<L||l>R) return inf;
if(l>=L&&r<=R) return t[u];
int mid=(l+r)>>1;
return min(query(L,R,l,mid,u<<1),query(L,R,mid+1,r,u<<1|1));
}//一个用于维护区间最小值的线段树
bool ans[maxn];
void work(){
sort(d+1,d+k+1,cmp1);
sort(s+1,s+q+1,cmp2);
int p=1;
for(int i=1;i<=q;i++){
while(p<=k&&d[p].fst<=s[i].x2){
update(d[p].sec,d[p].fst);
++p;
}
int que=query(s[i].y1,s[i].y2);
if(query(s[i].y1,s[i].y2)>=s[i].x1){
ans[s[i].id]=1;
}
}
}
void change(){
for(int i=1;i<=k;i++) swap(d[i].fst,d[i].sec);
for(int i=1;i<=q;i++){
swap(s[i].x1,s[i].y1);
swap(s[i].x2,s[i].y2);
}
memset(t,0,sizeof(t));
}//交换行列坐标扫列
int main(){
//freopen("matrix.in","r",stdin);
//freopen("matrix.out","w",stdout);
read(n),read(m),read(k),read(q);
mx=max(n,m);
for(int i=1;i<=k;i++){
read(d[i].fst),read(d[i].sec);
}
for(int i=1;i<=q;i++){
read(s[i].x1),read(s[i].y1),read(s[i].x2),read(s[i].y2);
s[i].id=i;
}
work();
change();
work();
for(int i=1;i<=q;i++){
if(ans[i]) printf("YES\n");
else printf("NO\n");
}
return 0;
}
D
中文题面

先放放肯定不是因为我不会凸包

浙公网安备 33010602011771号