假设 \(j>i\) ,则:\(i+l_i\le j-l_j,i+r_i\le j-r_j\)
所以相当于看区间 \([i+l_i,i+r_i]\) 和区间 \([j-r_j,j-l_j]\) 是否有交集
可以将这些区间放在数轴上,考虑建虚点,将数轴上的每个点向包含它的区间连边
但是这样会有一个问题,记加为右区间,减为左区间,此时就无法判断是哪种区间在相交
所以可以只保留既有左区间又有右区间覆盖的点,这样就能保证一定是符合条件的
但是依次对每个点连边显然会T,考虑优化
先用 \(pre\) 和 \(ne\) 记录前/后满足条件的最近的点
对于区间 \([l,r]\),操作就应在 \([ne_l,pre_r]\)
利用差分的思想,统计数轴上每个点被覆盖且不为末端点的次数
因为作为末端点,后面没有与之相邻的点,就无法向后合并
如果次数不为0,就向后连边,最后看有多少连通块即可
代码:
#include<cstdio>
#include<algorithm>
using namespace std;
int T,n,l[2000006],r[2000005],pre[2000005],ne[2000006];
int cntl[2000006],cntr[2000006],f[4000006],tot;
int v[2000005];
int find(int x)
{if(f[x]!=x) f[x]=find(f[x]);return f[x];
}
int main()
{scanf("%d",&T);while(T--){scanf("%d",&n);tot=0;for(int i=0;i<=n;i++){cntl[i]=cntr[i]=0;pre[i]=ne[i]=v[i]=0;}for(int i=1;i<=n;i++){scanf("%d%d",&l[i],&r[i]);cntl[max(1,i-r[i])]++;cntl[max(1,i-l[i]+1)]--;cntr[min(n,i+l[i])]++;cntr[min(n,i+r[i]+1)]--;f[i]=i;}for(int i=1;i<=n;i++){cntl[i]+=cntl[i-1];cntr[i]+=cntr[i-1];if(cntl[i]&&cntr[i]){pre[i]=ne[i]=++tot;f[n+tot]=tot+n;}else{pre[i]=pre[i-1];}}ne[n+1]=1e9;for(int i=n;i>0;i--){if(!ne[i]) ne[i]=ne[i+1];}for(int i=1;i<=n;i++){int ql=ne[max(1,i-r[i])];int qr=pre[max(0,i-l[i])];if(ql<=qr){v[ql]++,v[qr]--;f[find(i)]=find(qr+n);}ql=ne[min(n+1,i+l[i])];qr=pre[min(n,i+r[i])];if(ql<=qr){v[ql]++,v[qr]--;f[find(i)]=find(qr+n);}
// printf("%d",i);}for(int i=1;i<=tot;i++){v[i]+=v[i-1];}for(int i=1;i<=tot;i++){if(v[i]){f[find(i+n)]=find(n+i+1);}}int ans=0;for(int i=1;i<=n+tot;i++){if(f[i]==i) ans++;}printf("%d\n",ans);}return 0;
}