csp 模拟 八云蓝【计数】【线段树】

传送门

其实跟线段树没什么关系。

对于这道题,我们发现直接计数复杂度很大。比起对于每个询问,计算有多少个区间被调用,不如对于每个区间,计算有哪些询问调用了它。

对于一个询问,我们直接上线段树。然后接下来大力分类讨论。

1.不相交

不相交还做个鬼,直接跳过。

2.相交但不覆盖

对于一个相交但不覆盖,不容斥的话,分左右两种。容斥的话,就用总区间数减去不相交数。

我选择的不容斥(很难写,不推荐),很明显,只需要左右手玩一下等差数列就行,小心计算。

3.覆盖。

为了复杂度的保证,我们在覆盖后应该直接获得这个点的答案然后返回。

但事实上,这个点在线段树上所对应的子树中,每个区间都可能成为终止区间或者经过区间,这些我们不能一一获取。

所以只能预处理。对于每个点,预处理其子树中,每一个点作为终止区间或者经过区间的答案和。

①:作为终止区间

一个点作为终止区间,需要满足两个条件:其被覆盖,其父亲不被覆盖。

只计算其被覆盖,很简单:(l-ql+1)(qr-r+1)

对于第二个条件,我们容斥转换为:其被覆盖的方案-其父亲被覆盖的方案。

然后我们发现,除了叶子节点,一个线段树结点有两个儿子。所以它会被减两次。而它自己会加一次。所以总的来说,它实际上是做了一个负的贡献。

所以我们得到公式:如果是叶子节点,贡献为(l-ql+1)(qr-r+1),反之,在公式的基础上*-1。

②:作为经过区间

一个点作为经过区间,需要满足两个条件:与询问相交,不被询问覆盖。

有两种计算方式。一种是直接大力计算,一种是容斥计算。

大力计算:将方案分成两种:一个端点在l,r之外,和两个端点都在l,r之内。

对于一个端点在l,r之外,我们发现另一个端点能取的范围是[l,r-1]或者[l+1,r],所以每取一次贡献是(r-l),总贡献(r-l)(l-ql+1+qr-r+1)

对于都在端点内,实际上是两个点的范围是[l+1,r-1],等价于问一个长度为r-l-1的区间有多少个子区间,用等差数列计算得\frac{(r-l)(r-l-1)}{2}

加起来得贡献为(r-l)(l-ql+qr-r+2)+\frac{(r-l)(r-l-1)}{2}

 

容斥计算:方案为总方案-不相交的-覆盖的。

总方案:\frac{(qr-ql+1)(qr-ql+2)}{2},左边不相交的:\frac{(l-ql)(l-ql+1)}{2},右边不相交的:\frac{(qr-r)(qr-r+1)}{2},覆盖的:(l-ql+1)(qr-r+1)

合起来是\frac{(qr-ql+1)(qr-ql+2)}{2}-\frac{(l-ql)(l-ql+1)}{2}-\frac{(qr-r)(qr-r+1)}{2}-(l-ql+1)(qr-r+1)

 

完了,然后加起来。。

因为我的计算能力太弱算错了共计5次,用了三个小时调试,,

代码不能看,奇丑无比。

#include<bits/stdc++.h>
using namespace std;
#define in read()
#define int long long
int in{
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){
		ch=getchar();if(ch=='-')f=-1;
	}
	while(isdigit(ch)){
		cnt=cnt*10+ch-48;ch=getchar();
	}return cnt*f;
}
struct node{
	int l,r;
	int L,R,LR,gu;
}t[2000003];
void build(int u,int l,int r){
//	cout<<u<<" "<<l<<" "<<r<<endl;
	t[u].l=l;t[u].r=r;
	if(l==r){
		t[u].L=r-1;t[u].R=l+1;t[u].LR=-1;t[u].gu=1-l*r+l-r;
		t[u].L*=2;t[u].R*=2;t[u].LR*=2;t[u].gu*=2;
//		cout<<l<<" "<<r<<" "<<t[u].L<<" "<<t[u].R<<" "<<t[u].LR<<" "<<t[u].gu<<endl; 
		return;
	}int mid=(l+r)>>1;
	t[u].L=1-r;t[u].R=-1-l;t[u].LR=1;t[u].gu=l*r-1+r-l;
	t[u].L*=2;t[u].R*=2;t[u].LR*=2;t[u].gu*=2;
//	cout<<l<<" "<<r<<" "<<t[u].L<<" "<<t[u].R<<" "<<t[u].LR<<" "<<t[u].gu<<endl;
	t[u].L+=2*l-2*r;t[u].R+=2*r-2*l;t[u].gu+=4*l*r-2*l*l-2*r*r-4*l+4*r+r*r-2*r*l+l*l-r+l;

//	cout<<l<<" "<<r<<" "<<t[u].L<<" "<<t[u].R<<" "<<t[u].LR<<" "<<t[u].gu<<endl;
	build(u*2,l,mid);build(u*2+1,mid+1,r);
	
	t[u].L+=t[u*2].L+t[u*2+1].L;
	t[u].R+=t[u*2].R+t[u*2+1].R;
	t[u].LR+=t[u*2].LR+t[u*2+1].LR;
	t[u].gu+=t[u*2].gu+t[u*2+1].gu;
//	cout<<l<<" "<<r<<" "<<t[u].L<<" "<<t[u].R<<" "<<t[u].LR<<" "<<t[u].gu<<endl;
}
int query(int u,int ql,int qr){
//	cout<<u<<" "<<t[u].l<<" "<<t[u].r<<endl;
	int tmp=0;
	int mid=(t[u].l+t[u].r)>>1;
	
	if(ql<=t[u].l&&t[u].r<=qr){
//		cout<<t[u].l<<" "<<t[u].r<<" "<<t[u].L<<" "<<t[u].R<<" "<<t[u].LR<<" "<<t[u].gu<<" "<<t[u].x<<" "<<t[u*2].x<<" "<<t[u*2+1].x<<endl;
		return t[u].L*ql+t[u].R*qr+t[u].LR*ql*qr+t[u].gu;
	}if(t[u].l!=t[u].r){
		if(qr>=t[u].l&&ql<t[u].l&&qr<t[u].r){tmp+=(qr+t[u].l-2*ql+2)*(qr-t[u].l+1);}
		if(ql<=t[u].r&&qr>t[u].r&&ql>t[u].l){tmp+=(t[u].r-ql+1)*(2*qr-t[u].r-ql+2);}
//		if(ql<=t[u].l&&t[u].r<=qr){cout<<"o5p ";tmp+=t[u].r-t[u].l+(t[u].r-t[u].l)*(t[u].r-t[u].l+1)/2;}
		if(ql>=t[u].l&&qr<t[u].r||ql>t[u].l&&qr<=t[u].r){tmp+=(qr-ql+2)*(qr-ql+1);}
//		cout<<t[u].l<<" "<<t[u].r<<" "<<tmp<<endl;
	}
	if(ql<=mid)tmp+=query(u*2,ql,qr);
	if(mid<qr)tmp+=query(u*2+1,ql,qr);
	return tmp;
}
int n,q,op;
int last;
signed main(){
	n=in;q=in;op=in;build(1,1,n); 
	while(q--){
		int l=in;int r=in;
		l=(l^(last*op))%n+1;
		r=(r^(last*op))%n+1;
		if(l>r)swap(l,r);
//		cout<<l<<" "<<r<<endl;
		last=query(1,l,r)/2;
		cout<<last<<'\n';
	}
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
廊桥分配问题是指给定一座长度为n的廊桥以及m个人,每个人需要跨过廊桥到对面。廊桥每次只能让两个人同时通过,且只有两个人的速度加和不超过廊桥长度时才能通过。每个人过桥所需的时间不同,要求找到一种过桥方案使得所有人的总过桥时间最短。 该问题可以通过使用线段树的解法。首先,将n个位置看作是一棵树,每个节点对应一个位置。然后,我们将所有人按照过桥时间从小到大排序,并按照排序结果为每个节点分配一个排序编号。接下来,从左到右遍历排序后的人员列表,对于每个人,我们找到其对应的节点,并为该节点分配一个值,表示该位置可以被占用。 这样,在分配完所有人的节点后,我们得到了一个线段树,每个非叶子节点表示一个廊桥位置,叶子节点表示一个人,其父节点的值表示桥上人员的速度加和。通过遍历这颗树,可以计算出所有人过桥的最短总时间。 具体操作如下: 1. 根据所有人的过桥时间从小到大排序。 2. 为每个节点分配排序编号。 3. 初始化线段树的所有节点为空(未占用)。 4. 从左到右遍历排序后的人员列表,对于每个人: a. 找到对应的节点。 b. 判断该节点是否为空,如果为空,表示该位置可以被占用,否则找到该节点的兄弟节点(该节点的父节点的其他子节点)。 c. 将该节点或其兄弟节点标记为占用,并更新父节点的值。 5. 遍历线段树,计算所有人过桥的总时间。 使用线段树解决廊桥分配问题的时间复杂度为O(nlogn),因为排序的时间复杂度为O(nlogn),遍历人员列表的时间复杂度为O(n),遍历线段树的时间复杂度为O(nlogn)。总的空间复杂度为O(n)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值