「Gym102759B」Cactus Competition 题解
本文网址:https://www.cnblogs.com/zsc985246/p/17254351.html ,转载请注明出处。
传送门
「Gym102759B」Cactus Competition
题目大意
有一个 的网格图,一个长度为 的序列 ,和一个长度为 的序列 。
网格图中,第 行第 列的位置有一个数 。 表示这个位置可以通过。
一对 合法当且仅当 ,点 作为起点, 作为终点,只向下走或向右走能从起点到达终点。
求合法的 总数。
。
思路
由于 很大,而且题目给出 数组的方式很特殊,我们考虑这个图有什么性质。
首先,我们考虑从 到 怎么走。
对于一般的图,有可能会有下图情况,其中灰色的是不能走的位置:
回到本题的图中,我们可以发现,因为 ,又由图知 ,所以 ,也就是 。
根据 倒推得 ,所以 必定是不能走的。
那么拓展到一般情况,只要存在一个 使得 能走, 不能走,那么对于任意的 , 不能走时 必定不能走。
举个例子,下图所有橙色的位置都是不能走的。
竖着的不能走的段同理。
所以在本题中从 到 没有路只有四种情况:
-
有一整行不能走,即 ,使得 ;
-
有一整列不能走,即 ,使得 ;
-
不能走的位置将 围住了,即 ,使得 且 ;
-
不能走的位置将 围住了,即 ,使得 且 。
现在我们找到了图的性质,回归原来的问题,不固定起终点,考虑对这四种情况分别如何计算答案。
直接计算不太好想,我们考虑计算不合法的数量。按照不合法的四种情况进行讨论:
-
令第 行不能走,那么 不合法。
-
令第 列的 行到 行之间不能走,那么 不合法。
-
令不能走的位置的交叉处坐标为 ,最上方的不能走的格子纵坐标为 ,那么 不合法。
-
令不能走的位置的交叉处坐标为 ,最下方的不能走的格子纵坐标为 ,那么 不合法。
我们发现,每种情况都是对 做出了限制,直接乘起来会计算重复,所以可以根据套路,将其转化为矩形面积,使用扫描线处理。
套路:两个变量同时受到限制,答案用到这两个变量的乘积,且直接算会算重的时候,可以使用扫描线。
但是这里需要注意 。这个的处理方法有两种,一种是查询时只查绿色部分;一种是暴力加 个阶梯状的蓝色矩形。代码中使用的是后者。
最后,我们需要使用二分、ST 表和前后缀最值来加快处理速度,具体实现见代码。
总复杂度 。
注意把数组大小算对!代码中的数组是卡范围开的,可以用作参考。
代码实现
小提示: y1
不能用作全局变量名。
#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
const ll N=2e5+10;
using namespace std;
struct node{
ll x,y1,y2;//位置
ll opt;//类型
}line[N*10];//线段
ll cnt;//线段个数
bool cmp(node a,node b){//按x坐标排序
return a.x<b.x;
}
struct ST{//ST表
ll log[N];
ll f[N][20];
void init(ll n,ll a[]){//预处理
log[0]=-1;
For(i,1,n)log[i]=log[i>>1]+1,f[i][0]=a[i];
For(j,1,log[n]){
For(i,1,n-(1<<j)+1){
f[i][j]=max(f[i][j-1],f[i+(1<<j-1)][j-1]);
}
}
}
ll query(ll l,ll r){//查询
if(l>r)return 1145141919810;
ll t=log[r-l+1];
return max(f[l][t],f[r-(1<<t)+1][t]);
}
}ST_a;
struct SEG{//线段树
#define lson rt<<1
#define rson rt<<1|1
ll tot[N*40],lazy[N*40];
void pushup(ll rt,ll l,ll r){
if(lazy[rt]>0)tot[rt]=r-l+1;
else tot[rt]=tot[lson]+tot[rson];
}
void change(ll rt,ll l,ll r,ll x,ll y,ll z){
if(x<=l&&r<=y){
lazy[rt]+=z;
pushup(rt,l,r);
return;
}
ll mid=l+r>>1;
if(x<=mid)change(lson,l,mid,x,y,z);
if(y>mid)change(rson,mid+1,r,x,y,z);
pushup(rt,l,r);
}
}seg;
ll n,m,k,q;
ll a[N];
ll b[N];
ll pmin[N],pmax[N];//前缀最值
ll smin[N],smax[N];//后缀最值
void add(ll x1,ll x2,ll y1,ll y2){//将矩形转化为线段
if(x1>x2||y1>y2)return;
x2++;
line[++cnt]=(node){x1,y1,y2,1};
line[++cnt]=(node){x2,y1,y2,-1};
}
void mian(){
scanf("%lld",&n);
scanf("%lld",&m);
For(i,1,n){
scanf("%lld",&a[i]);
}
For(i,1,m){
scanf("%lld",&b[i]);
}
//最值预处理
ST_a.init(n,a);
pmax[0]=smax[m+1]=-1e9;
pmin[0]=smin[m+1]=1e9;
For(i,1,m){
pmax[i]=max(pmax[i-1],b[i]);
pmin[i]=min(pmin[i-1],b[i]);
}
Rep(i,m,1){
smax[i]=max(smax[i+1],b[i]);
smin[i]=min(smin[i+1],b[i]);
}
//1. 一整行不能走
For(i,1,n){
if(a[i]+pmax[m]<0){
add(1,i,i,n);
}
}
//2. 一列的一段连续区间不能走
For(j,1,m){
if(b[j]==pmin[m]){//只需要找最小位置
ll l=1,r=1;
while(l<=n){
while(a[r]+b[j]<0&&r<=n)r++;
add(l,r-1,l,r-1);
l=r=r+1;
}
break;
}
}
//3. 一个边框把起点围住了
For(i,1,n){
ll l,r;
//二分找出最右端位置
l=1,r=m;
ll j=0;
while(l<=r){
ll mid=l+r>>1;
if(pmax[mid]+a[i]<0)l=mid+1,j=mid;
else r=mid-1;
}
if(!j)continue;
//二分找出最大长度
l=1,r=i;
ll t=i;
while(l<=r){
ll mid=l+r>>1;
if(pmin[j]+ST_a.query(mid,i)<0)r=mid-1,t=mid;
else l=mid+1;
}
add(t,i,t,n);
}
//4. 一个边框把终点围住了
For(i,1,n){
ll l,r;
//二分找出最左端位置
l=1,r=m;
ll j=m+1;
while(l<=r){
ll mid=l+r>>1;
if(smax[mid]+a[i]<0)r=mid-1,j=mid;
else l=mid+1;
}
if(j>m)continue;
//二分找出最大长度
l=i,r=n;
ll t=i;
while(l<=r){
ll mid=l+r>>1;
if(smin[j]+ST_a.query(i,mid)<0)l=mid+1,t=mid;
else r=mid-1;
}
add(1,t,i,t);
}
//添加阶梯状矩阵
For(i,1,n)add(i,i,1,i-1);
//排序
sort(line+1,line+cnt+1,cmp);
//扫描线
ll ans=0;
For(i,1,cnt){
if(i>1)ans+=(line[i].x-line[i-1].x)*seg.tot[1];
seg.change(1,1,n,line[i].y1,line[i].y2,line[i].opt);
}
//总体减去不合法
printf("%lld",n*n-ans);
}
int main(){
int T=1;
// scanf("%d",&T);
while(T--)mian();
return 0;
}
尾声
如果你发现了问题,你可以直接回复这篇题解
如果你有更好的想法,也可以直接回复!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!