JZOJ 5919. 【NOIP2018模拟10.22】逛公园

题目

n条线段 [ l , r ] [l,r] [l,r],有m组询问 [ x , y ] [x,y] [x,y],求有多少个 [ x , y ] [x,y] [x,y]的子区间(含 [ x , y ] [x,y] [x,y])不覆盖任意一条线段。

题解

顺便复习了下tarjan求点双。
审题!
去重貌似是很麻烦的。
用比较简单的方法。
突破口:固定住左端点,扩右端点,直到不能扩为止。通过这样来计算。
m x [ i ] mx[i] mx[i]表示以i为左端点,右端点最多能到哪。
显然, m x [ i ] mx[i] mx[i]是递增的。
所以,在 [ x , y ] [x,y] [x,y]中二分出一个点z, i ∈ [ z , y ] i∈[z,y] i[z,y],mx[i]>y
区间 [ z , y ] [z,y] [z,y]的所有子区间都是合法的。而 i ∈ [ x , z ) i∈[x,z) i[x,z),对答案的贡献为 m x [ i ] − i + 1 mx[i]-i+1 mx[i]i+1,用树状数组维护个前缀和就好了。

小结

①第一突破口,固定住左端点,扩右端点,直到不能扩为止。
②第二突破口, m x [ i ] mx[i] mx[i]是递增的。
③位置i对答案的贡献为关于i的函数 c o n t r i b u t e ( i ) contribute(i) contribute(i)

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 600010
#define LL long long
#define P(a) putchar(a)
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--) 
using namespace std;
struct Graph{
    int tot,head[N],next[N<<1],to[N<<1];
	void lb(int x,int y){to[++tot]=y;next[tot]=head[x];head[x]=tot;}
}G;
int i,j,k,l1,r1,n,m,q,cs;
int Mx,Mn,u,v,w,wz,mid;
int r[N];
int opl,opr,opx,opz,TOT;
int dfn[N],low[N],st[N],T,top;
bool Bz[N];
LL tr[N];
LL ans;
int read(){
	int fh=0,rs=0;char ch=0;
	while((ch<'0'||ch>'9')&&(ch^'-'))ch=getchar();
	if(ch=='-')fh=1,ch=getchar();
	while(ch>='0'&&ch<='9')rs=(rs<<3)+(rs<<1)+(ch^'0'),ch=getchar();
	return fh?-rs:rs;
}
void write(int x){
	if(x>9)write(x/10);
	P(x%10+'0');
}
int Min(int x,int y){return x<y?x:y;}
int Max(int x,int y){return x>y?x:y;}
void tarjan(int x){
	int i,c0;
	Bz[x]=1;
	st[++top]=x;
	dfn[x]=low[x]=++T;
	for(i=G.head[x];i;i=G.next[i])
	    if(!dfn[G.to[i]]){
	    	tarjan(G.to[i]);
	    	low[x]=Min(low[x],low[G.to[i]]);
	    	if(low[G.to[i]]>=dfn[x]){
	    		Mn=Mx=x;
	    		c0=0;
	    		while(st[top+1]^G.to[i]){
	    			c0++;
	    			Mn=Min(Mn,st[top]);
	    			Mx=Max(Mx,st[top]);
	    			top--;
				}
				if(c0>1){
					r[Mn]=Min(r[Mn],Mx-1);
				}
			}
		}else low[x]=Min(low[x],dfn[G.to[i]]);
}
int lowbit(int x){return x&(-x);}
void add(int x,LL z){
	for(;x<=n;x=x+lowbit(x))tr[x]=tr[x]+z;
}
LL qry(int x){
	LL rs=0;
	for(;x;x=x-lowbit(x))rs=rs+tr[x];
	return rs;
}
LL calc(LL x){return x*(x+1)>>1;}
int main(){
	n=read();m=read();
	fo(i,1,n)r[i]=n;
	r[n+1]=n+1;
	fo(i,1,m){
		u=read(),v=read();
		G.lb(u,v);
		G.lb(v,u);
	}
	fo(i,1,n)if(!Bz[i]){
		tarjan(i);
	}
	fd(i,n-1,1)r[i]=Min(r[i+1],r[i]);
	fo(i,1,n)add(i,r[i]-i+1);
	q=read();
	fo(cs,1,q){
		u=read(),v=read();
		l1=u,r1=v+1;w=r1;
		while(l1<=r1){
			mid=(l1+r1)>>1;
			if(r[mid]>v)w=mid,r1=mid-1;else l1=mid+1;
		}
		ans=qry(w-1)-qry(u-1)+calc(v-w+1);
		printf("%lld\n",ans);
	}
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值