背景:
又一次代码不知所踪。
T1 \text{T1} T1:
que \text{que} que:
多次操作对一段区间染黑色,求每一次操作后黑色线段的数量。
例子:
假设我们对
5
−
5
,
5
−
6
5-5,5-6
5−5,5−6染色,由于这两个区间并有交集,因此黑色线段的数量为
1
1
1。
假设我们对
5
−
5
,
6
−
6
5-5,6-6
5−5,6−6染色,由于这两个区间并没有交集,因此黑色线段的数量为
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)
∑l≤xi≤xj≤r(xj−xi)。
sol \text{sol} sol:
一种显然的想法(其实是我自己的想法):离散化后用一棵线段树维护坐标的前缀和以及排名,将这两个东西相乘就是某个点的贡献,再用一棵线段树维护每一个点的贡献的前缀和。那就可以用
r
r
r位置的前缀和减去
l
−
1
l-1
l−1位置的前缀和出解了。修改时可以
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+sizel∗totr−sizer∗totl。
理解起来也不难,左边对左边的贡献在
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
sizel∗totr;同理左边对右边由负贡献,同样计算即可,为
−
s
i
z
e
r
∗
t
o
t
l
-size_r*tot_l
−sizer∗totl。
时间复杂度:
Θ
(
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
xi−k≤xj≤xi+k 。这个操作可以使用线段树维护。
对于每个询问可以先求出同时作为两个组员组长的坐标范围,再在这些点中查询最大组员人数,将询问离线,用扫描线从左至右扫过去更新即可,同样可以使用线段树维护。
时间复杂度:
Θ
(
n
log
n
)
\Theta(n \log n)
Θ(nlogn)。
code \text{code} code:
咕咕咕
...
\text{...}
...
或许有空会补。