杭二学习Day3——比赛

背景:

又一次代码不知所踪。

T1 \text{T1} T1

que \text{que} que

多次操作对一段区间染黑色,求每一次操作后黑色线段的数量。
例子:
假设我们对 5 − 5 , 5 − 6 5-5,5-6 55,56染色,由于这两个区间并有交集,因此黑色线段的数量为 1 1 1
假设我们对 5 − 5 , 6 − 6 5-5,6-6 55,66染色,由于这两个区间并没有交集,因此黑色线段的数量为 2 2 2

sol \text{sol} sol

一开始读错了题,以为上面第二种情况的方案数为 1 1 1,毫不犹豫地敲起了线段树。
线段树维护区间黑色点的个数以及黑色线段的数量。
后来发现样例跑不过,问了 brz \text{brz} brz,才发现自己读错题了。
不过,我们特判一下是否有交集好像也能过样例。
时间复杂度: Θ ( n log ⁡ 2 n ) \Theta(n\log^2n) Θ(nlog2n)

然而正确的复杂度是 Θ ( n log ⁡ n ) \Theta(n\log n) Θ(nlogn)的。
做法更加容易。
set \text{set} set维护线段的端点,按照左端点升序,每插入一条线段,找到对应位置后看看能否向左或向右拓展,若可以,则更新线段的端点。
最后 set \text{set} set的大小即为所求。
由于每一次可能使线段合并,而最多有 n n n条线段,合并 n n n次,故时间复杂度得证。

code \text{code} code
update \text{update} update 2019.8.8 2019.8.8 2019.8.8

#include<cstdio>
#include<cstring>
#include<set>
#include<algorithm>
#define ID set<node>::iterator
using namespace std;
struct node
{
	int x,y;
	friend bool operator <(const node &x,const node &y)
	{
		return x.x<y.x;
	}
};
set<node> f;
	int n,ans=0;
int work(int x,int y)
{
	int t1=x,t2=y;
	ans++;
	{
		ID op=f.lower_bound((node){x,0});
		op--;
		if(op->y>=x)
		{
			t1=min(t1,op->x);
			t2=max(t2,op->y);
			f.erase(op);
			ans--;
		}
	}
	{
		ID op=f.lower_bound((node){x,0});
		while(op->x<=y)
		{
			t2=max(t2,op->y);
			f.erase(op);
			op=f.lower_bound((node){x,0});
			ans--;
		}
	}
	f.insert((node){t1,t2});
	return ans;
}
int main()
{
	int x,y;
	scanf("%d",&n);
	f.insert((node){0,0});
	f.insert((node){2147483647,2147483647});
	for(int i=1;i<=n;i++)
	{
		scanf("%d %d",&x,&y);
		if(x>y) swap(x,y);
		printf("%d\n",work(x,y));
	}
}


T2 \text{T2} T2

que \text{que} que

输入 n n n个点的坐标 x i , i ∈ [ 1 , n ] x_i,i∈[1,n] xi,i[1,n]
支持两种操作:
[ 1 ] . [1]. [1].将第 x x x个点的坐标改为 y y y
[ 2 ] . [2]. [2]. ∑ l ≤ x i ≤ x j ≤ r ( x j − x i ) \sum_{l≤x_i≤x_j≤r}(x_j-x_i) lxixjr(xjxi)

sol \text{sol} sol

一种显然的想法(其实是我自己的想法):离散化后用一棵线段树维护坐标的前缀和以及排名,将这两个东西相乘就是某个点的贡献,再用一棵线段树维护每一个点的贡献的前缀和。那就可以用 r r r位置的前缀和减去 l − 1 l-1 l1位置的前缀和出解了。修改时可以 logn \text{logn} logn修改第一课线段树的坐标前缀和和排名;对于第二棵线段树,我们可以倍增的让它到我们想要的排名。
时间复杂度: Θ ( n log ⁡ 2 n ) \Theta(n\log^2n) Θ(nlog2n)
代码复杂度:你打打试试。
然而这样的代码复杂度极高,考场上用了 1h \text{1h} 1h去肝,然而发现及其难以调试,最后时间不够了,弃疗了。

当然,上面也不是正解。
离不开线段树的比赛。
先离散化。
考虑如何用线段树直接维护 a n s ans ans
显然可以用线段树维护 s i z e size size(区间坐标的数量) 和 t o t tot tot(区间坐标的总和)。
若区间在左子树,则 a n s n o w = a n s l ans_{now}=ans_l ansnow=ansl
若区间在左子树,则 a n s n o w = a n s r ans_{now}=ans_r ansnow=ansr
若区间横跨左右子树,则 a n s n o w = a n s l + a n s r + s i z e l ∗ t o t r − s i z e r ∗ t o t l ans_{now}=ans_{l}+ans_{r}+size_l*tot_r-size_r*tot_l ansnow=ansl+ansr+sizeltotrsizertotl
理解起来也不难,左边对左边的贡献在 a n s l ans_l ansl里,右边对右边的贡献在 a n s r ans_r ansr里,我们知道右边对左边有正贡献,每一个右边的点对应 s i z e l size_l sizel个左边的点,提取一下,得到 s i z e l ∗ t o t r size_l*tot_r sizeltotr;同理左边对右边由负贡献,同样计算即可,为 − s i z e r ∗ t o t l -size_r*tot_l sizertotl
时间复杂度: Θ ( n log ⁡ n ) \Theta(n\log n) Θ(nlogn)

code \text{code} code

update \text{update} update 2019.8.9 。 \text{2019.8.9}。 2019.8.9

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#define LL long long
using namespace std;
map<int,int> MAP;
	int n,m,len=0;
	int a[500010],d[500010];
	struct node1{int l,r,lc,rc,size;LL tot,ans;} tr[500010];
	struct node2{int t,x,y;} b[500010];
void build(int l,int r)
{
	int now=++len,mid=(l+r)>>1;
	tr[now]=(node1){l,r,-1,-1,0,0,0};
	if(l<r)
	{
		tr[now].lc=len+1,build(l,mid);
		tr[now].rc=len+1,build(mid+1,r);
	}
}
void change(int now,int x,int k,int flag)
{
	if(tr[now].l==tr[now].r)
	{
		tr[now].tot+=k*flag;
		tr[now].size+=flag;
		return;
	}
	int lc=tr[now].lc,rc=tr[now].rc,mid=(tr[now].l+tr[now].r)>>1;
	if(x<=mid) change(lc,x,k,flag); else change(rc,x,k,flag);
	tr[now].size=tr[lc].size+tr[rc].size;
	tr[now].tot=tr[lc].tot+tr[rc].tot;
	tr[now].ans=tr[lc].ans+tr[rc].ans+(LL)tr[lc].size*tr[rc].tot-(LL)tr[rc].size*tr[lc].tot;
}
node1 getsum(int now,int l,int r)
{
	if(tr[now].l==l&&tr[now].r==r) return tr[now];
	int lc=tr[now].lc,rc=tr[now].rc,mid=(tr[now].l+tr[now].r)>>1;
	if(r<=mid) return getsum(lc,l,r);
	else if(l>mid) return getsum(rc,l,r);
	else
	{
		node1 t1=getsum(lc,l,mid),t2=getsum(rc,mid+1,r);
		return (node1){0,0,0,0,t1.size+t2.size,t1.tot+t2.tot,t1.ans+t2.ans+(LL)t1.size*t2.tot-(LL)t2.size*t1.tot};
	}
}
int main()
{
	int op=0,tmp=0;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		d[++op]=a[i];
	}
	scanf("%d",&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%d %d %d",&b[i].t,&b[i].x,&b[i].y);
		if(b[i].t==2) d[++op]=b[i].x;
		d[++op]=b[i].y;
	}
	sort(d+1,d+op+1);
	for(int i=1;i<=op;i++)
		if(!MAP[d[i]]) MAP[d[i]]=++tmp;
	build(1,tmp);
	for(int i=1;i<=n;i++)
		change(1,MAP[a[i]],a[i],1);
	for(int i=1;i<=m;i++)
		if(b[i].t==1)
		{
			change(1,MAP[a[b[i].x]],a[b[i].x],-1);
			a[b[i].x]=b[i].y;
			change(1,MAP[a[b[i].x]],a[b[i].x],1);
		}
		else
		{
			printf("%lld\n",getsum(1,MAP[b[i].x],MAP[b[i].y]).ans);
		}
}


T3 \text{T3} T3

que \text{que} que

n n n个人,每一个人有一个能力值和默契值。这些人可以任意组队,定义组长为组里能力值最高的人(可以并列最高,但只有一个组长)组员与组长之间的默契值的绝对值不超过 k k k。多组询问,给出 x , y x,y x,y,问这两个人在同一组时,这一组最多能有多少人。

sol \text{sol} sol

看起来很玄妙。
以默契值为横坐标,能力值为纵坐标,每个人都可以看作平面上的一个点。先将横坐标离散化,然后将每个点以纵坐标为关键字排序,按能力值升序插入每个点并计算出以第 i i i个点为组长的组员人数,组员 j j j的坐标应满足 x i − k ≤ x j ≤ x i + k x_i-k≤x_j≤x_i+k xikxjxi+k 。这个操作可以使用线段树维护。
对于每个询问可以先求出同时作为两个组员组长的坐标范围,再在这些点中查询最大组员人数,将询问离线,用扫描线从左至右扫过去更新即可,同样可以使用线段树维护。
时间复杂度: Θ ( n log ⁡ n ) \Theta(n \log n) Θ(nlogn)

code \text{code} code

咕咕咕 ... \text{...} ...
或许有空会补。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值