线段树

板子题---单点更新,区间和

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
#define ll long long
//注意点的位置要对应,下文都要以rt,l,r为顺序
#define m ((l+r)>>1)
#define lson rt<<1,l,m
#define rson rt<<1|1,m+1,r

const int maxn=50000+5;
int sum[maxn<<2];
更新父节点
void pushup(int rt)
{
    sum[rt]=sum[rt<<1]+sum[rt<<1|1];  //父节点的区间值是两个子节点的和
}
建树
void build(int rt,int l,int r)
{
    if(l==r)
        scanf("%d",&sum[rt]);   //在最底层也就是区间只有一个点的时候赋初值,通过递归把整个树附上初值
    else
    {
        build(lson);  //递归
        build(rson);
        pushup(rt);  //更新父节点
    }
}
区间查询
int query(int rt,int l,int r,int L,int R)  //初始lr是1和n,LR是区间所以会进入else里,最后返回的是一个凑的值
{
    if(L<=l&&r<=R)
        return sum[rt];  //递归停止的地方
    else
    {
        int temp=0;
        if(L<=m)
            temp+=query(lson,L,R);
        if(R>m)
            temp+=query(rson,L,R);
        return temp;
    }
}
区间更改(单点更改)
void update(int rt,int l,int r,int L,int R)  //L是点,R是它增加或减少的值
{
    if(l==r&&r==L)
        sum[rt]+=R;   //先给最底层更新,然后自下而上更新
    else
    {
        if(L<=m)
            update(lson,L,R);
        else
            update(rson,L,R);
        pushup(rt);
    }
}
int main()
{
    int t,n,a,b;
    char ha[10];
    scanf("%d",&t);
   for(int i=1;i<=t;i++)
    {
        scanf("%d",&n);  //输入个数
        build(1,1,n);  //直接建树,在建树时为点赋初值
        printf("Case %d:\n",i);
        while(~scanf("%s",ha)) 
        {
            if(ha[0]=='E')
                break;
            scanf("%d %d",&a,&b);
            if(ha[0]=='Q')   //查询
                printf("%d\n",query(1,1,n,a,b));
            else if(ha[0]=='A')  //增加
                update(1,1,n,a,b);
            else  //减少
                update(1,1,n,a,-b);
        }
    }
}

I hate it 单点更新,区间最大值

与上一题一样,就是求和公式改为最大值公式...
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
#define ll long long
//注意点的位置要对应,下文都要以rt,l,r为顺序
#define m ((l+r)>>1)
#define lson rt<<1,l,m
#define rson rt<<1|1,m+1,r

const int maxn=200000+5;
int Max[maxn<<2];
void pushup(int rt)
{
    Max[rt]=max(Max[rt<<1],Max[rt<<1|1]);  //父节点的区间值是两个子节点的和
}
void build(int rt,int l,int r)
{
    if(l==r)
        scanf("%d",&Max[rt]);   //在最底层也就是区间只有一个点的时候赋初值,通过递归把整个树附上初值
    else
    {
        build(lson);  //递归
        build(rson);
        pushup(rt);  //更新父节点
    }
}
int query(int rt,int l,int r,int L,int R)  //初始lr是1和n,LR是区间所以会进入else里,最后返回的是一个凑的值
{
    if(L<=l&&r<=R)
        return Max[rt];  //递归停止的地方
    else
    {
        int temp=-maxn-500;
        if(L<=m)
            temp=max(temp,query(lson,L,R));
        if(R>m)
            temp=max(temp,query(rson,L,R));
        return temp;
    }
}
void update(int rt,int l,int r,int L,int R)  //L是点,R是它增加或减少的值
{
    if(l==r&&r==L)
        Max[rt]=R;   //先给最底层更新,然后自下而上更新
    else
    {
        if(L<=m)
            update(lson,L,R);
        else
            update(rson,L,R);
        pushup(rt);
    }
}
int main()
{
    int n,t,a,b;
    char ha[10];
    while(~scanf("%d %d",&n,&t))
    {
        build(1,1,n);  //直接建树,在建树时为点赋初值
        for(int i=0; i<t; i++)
        {
            scanf("%s %d %d",ha,&a,&b);
            if(ha[0]=='Q')   //查询
                printf("%d\n",query(1,1,n,a,b));
                else
                update(1,1,n,a,b);

        }
    }
}

单点更新,区间最大最小值

求区间内ax*ay的最小值,注意x可以等于y!

同时求最大值最小值,最后根据正负情况判断使用最大值还是最小值

1 如果最大值最小值都是正数,用最小值*最小值

2 如果两个数都是负数,用最大值*最大值

3 如果一正一负,那就是这两个数的乘积

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
#define ll long long
//注意点的位置要对应,下文都要以rt,l,r为顺序
#define m ((l+r)>>1)
#define lson rt<<1,l,m
#define rson rt<<1|1,m+1,r

const int maxn=200000+5;
ll Max[maxn<<2],Min[maxn<<2],sum;
void pushup(ll rt)
{
    Max[rt]=max(Max[rt<<1],Max[rt<<1|1]);  //父节点的区间值是两个子节点的和
    Min[rt]=min(Min[rt<<1],Min[rt<<1|1]);
}
void build(ll rt,ll l,ll r)
{
    if(l==r)//在最底层也就是区间只有一个点的时候赋初值,通过递归把整个树附上初值
    {
        scanf("%lld",&Max[rt]);
        Min[rt]=Max[rt];
    }
    else
    {
        build(lson);  //递归
        build(rson);
        pushup(rt);  //更新父节点
    }
}
ll query(ll rt,ll l,ll r,ll L,ll R)  //区间最大值
{
    if(L<=l&&r<=R)
        return Max[rt];  //递归停止的地方
    ll temp=-maxn-500;
    if(L<=m)
        temp=max(temp,query(lson,L,R));
    if(R>m)
        temp=max(temp,query(rson,L,R));
    return temp;
}
ll queryy(ll rt,ll l,ll r,ll L,ll R)  //区间最小值
{
    if(L<=l&&r<=R)
        return Min[rt];  //递归停止的地方
    ll temp=maxn+500;
    if(L<=m)
        temp=min(temp,queryy(lson,L,R));
    if(R>m)
        temp=min(temp,queryy(rson,L,R));
    return temp;
}
void update(ll rt,ll l,ll r,ll L,ll R)  //L是点,R是它增加或减少的值
{
    if(l==r&&r==L)
    {
        Max[rt]=R;
        Min[rt]=R;
    }   //先给最底层更新,然后自下而上更新
    else
    {
        if(L<=m)
            update(lson,L,R);
        else
            update(rson,L,R);
        pushup(rt);
    }
}
int main()
{
    ll n,t,p,a,b,c;
    char ha[10];
    scanf("%lld",&t);
    while(t--)
    {
        scanf("%lld",&n);
        ll j=1;
        for(ll i=0; i<n; i++)
            j*=2;
        n=j;
        sum=0;
        build(1,1,n); //直接建树,在建树时为点赋初值
        scanf("%lld",&p);
        for(ll i=0; i<p; i++)
        {
            scanf("%lld %lld %lld",&c,&a,&b);
            if(c==1)   //查询
            {
                ll x=queryy(1,1,n,a+1,b+1),y=query(1,1,n,a+1,b+1);
                if(x<0&&y<0)
                    printf("%lld\n",y*y);
                else if(x<0&&y>=0)
                    printf("%lld\n",x*y);
                else
                    printf("%lld\n",x*x);
            }
            else
                update(1,1,n,a+1,b);
        }
    }
}

单点更新-最小逆反数

其实这道题用到线段数的部分就是求原逆反数,之后变化的逆反数是有规律的,直接按规律来求就好了

我们可以这样想:逆反数就是找x的前面有没有比他大的数,如果有y个,那么x的逆反数就是y,这样算好每一个数有多少个逆反数,就可以推规律了。首先,每个开头的数字的逆反数是0,但是它会成为后面数字的逆反数,也就是后面的比他小的数字的逆反数,这个数量是x,把它加到末尾,那么前面比他大的数字的个数就是n-x+1;例如,2在最开始的时候后面比他小的有0,1,逆反数是2,在末位的时候前面比他大的就是3,4,5,6,7,8,9,逆反数是7

规律也有啦,但是用线段树怎么找到原始序列的逆反数呢?

正如我们前面说的那样,找前面出现过的比他小的数量,这时候我们应该建一棵空树,每输入一个数进行一次查询,查询x->n-1中有没有出现过的数字(在这个数字之前出现),查询完后给这个数的值更新为1代表出现过,出现过就计算出现了几个。

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
#define ll long long
//注意点的位置要对应,下文都要以rt,l,r为顺序
#define m ((l+r)>>1)
#define lson rt<<1,l,m
#define rson rt<<1|1,m+1,r

const int maxn=5000+5;
int sum[maxn<<2];
void pushup(int rt)
{
    sum[rt]=sum[rt<<1]+sum[rt<<1|1];  //最大值和最小值
}
void build(int rt,int l,int r)
{
    sum[rt]=0;
    if(l==r)//在最底层也就是区间只有一个点的时候赋初值,通过递归把整个树附上初值
        return;
    else
    {
        build(lson);  //递归
        build(rson);
        pushup(rt);  //更新父节点
    }
}
int query(int rt,int l,int r,int L,int R)  //初始lr是1和n,LR是区间所以会进入else里,最后返回的是一个凑的值
{
    if(L<=l&&r<=R)
        return sum[rt];  //递归停止的地方
    int temp=0;
    if(L<=m)   //精华
        temp+=query(lson,L,R);
    if(R>m)
        temp+=query(rson,L,R);
    return temp;
}
void update(int rt,int l,int r,int L)  //更新,这个函数就是为了查找到最低层然后更新
{
    if(l==r&&r==L)
        sum[rt]++;  //先给最底层更新,然后自下而上更新
    else
    {
        if(L<=m)  
            update(lson,L);
        else
            update(rson,L);
        pushup(rt);
    }
}
int main()
{
    int n,a[5005],ans=0;
    while(~scanf("%d",&n))
    {
        build(1,0,n-1);  //建空树
        for(int i=0; i<n; i++)
        {
            scanf("%d",&a[i]);
            ans+=query(1,0,n-1,a[i],n-1);  //范围a[i]->n-1;
            update(1,0,n-1,a[i]);
        }
        int minn=ans;
        for(int i=0; i<n; i++)  //规律
        {
            ans=ans-a[i]+n-a[i]-1;
            if(ans<minn)
                minn=ans;
        }
        printf("%d\n",minn);
    }
}

区间更新

题目链接

鱼钩上连续的金属棒编号,从1到n。每一次操作,Pudge都可以将X到Y编号的金属棒,变成铜棒、银棒或金棒。挂钩的总价值是用N根金属棒的总价值来计算的。更准确地说,每种木棍的价值计算如下:对于每根铜棒,其值为1。每根银棒的价值是2。每根金棒的价值是3。Pudge想知道执行操作后钩子的总价值。你可以认为最初的钩子是由铜棒组成的

#include<iostream>
#include<cstdio>
#include<cmath>
#include<string.h>
using namespace std;
#define ll long long
//注意点的位置要对应,下文都要以rt,l,r为顺序
#define m ((l+r)>>1)
#define lson rt<<1,l,m
#define rson rt<<1|1,m+1,r

const int maxn=100000+5;
int sum[maxn<<2],add[maxn<<2];
void pushup(int rt)
{
    sum[rt]=sum[rt<<1]+sum[rt<<1|1];  //最大值和最小值
}
void build(int rt,int l,int r)
{
    if(l==r)//在最底层也就是区间只有一个点的时候赋初值,通过递归把整个树附上初值
        sum[rt]=1;
    else
    {
        build(lson);  //递归
        build(rson);
        pushup(rt);  //更新父节点
    }
}
void pushdown(int rt,int len)
{
    if(add[rt])
    {
        add[rt<<1]=add[rt];
        add[rt<<1|1]=add[rt];
        sum[rt<<1]=add[rt]*(len-(len>>1));
        sum[rt<<1|1]=add[rt]*(len>>1);
        add[rt]=0;
    }

}
int query(int rt,int l,int r,int L,int R)  //初始lr是1和n,LR是区间所以会进入else里,最后返回的是一个凑的值
{
    if(L<=l&&r<=R)
        return sum[rt];//递归停止的地方
    pushdown(rt,r-l+1);
    int temp=0;
    if(L<=m)   //精华
        temp+=query(lson,L,R);
    if(R>m)
        temp+=query(rson,L,R);
    return temp;
}

void update(int rt,int l,int r,int L,int R,int Z)  //更新,这个函数就是为了查找到最低层然后更新
{
    if(L<=l&&r<=R)
    {
        add[rt]=Z;
        sum[rt]=Z*(r-l+1);
        return;
    }
    pushdown(rt,r-l+1);
    if(L<=m)
        update(lson,L,R,Z);
    if(R>m)
        update(rson,L,R,Z);
    pushup(rt);
}
int main()
{
    int n,t,q,a,b,c;
    scanf("%d",&t);
    for(int op=1; op<=t; op++)
    {
        scanf("%d",&n);
        memset(add,0,sizeof(add));
        build(1,1,n);
        scanf("%d",&q);
        while(q--)
        {
            scanf("%d %d %d",&a,&b,&c);
            update(1,1,n,a,b,c);
        }
        printf("Case %d: The total value of the hook is %d.\n",op,query(1,1,n-1,1,n-1));
    }
}

线段树维护连续区间,开数组sum[],ls[],rs[] 分别表示当前节点最长连续区间长度,以左端点为开端的连续区间长度,以右端点为结束的连续区间长度

#include<iostream>
#include<cstdio>
#include<cmath>
#include<string.h>
using namespace std;
#define ll long long
//注意点的位置要对应,下文都要以rt,l,r为顺序
#define m ((l+r)>>1)
#define lson rt<<1,l,m
#define rson rt<<1|1,m+1,r
const int maxn=100000+5;

//这个代码的连续空间表示为空闲的连续空间,具体可根据题意更改,三个连续空间数组要一起更新
int sum[maxn<<2],add[maxn<<2],ls[maxn<<2],rs[maxn<<2];
void pushdown(int rt,int l, int r)
{
    if(add[rt]!=-1)
    {
        add[rt<<1]=add[rt<<1|1]=add[rt];
        if(add[rt]==1)   //全部占用,空闲区间长度为0
        {
            sum[rt<<1]=sum[rt<<1|1]=0;
            ls[rt<<1]=ls[rt<<1|1]=rs[rt<<1]=rs[rt<<1|1]=0;
        }
        else   //清空
        {
            sum[rt<<1]=ls[rt<<1]=rs[rt<<1]=(m-l+1);
            sum[rt<<1|1]=ls[rt<<1|1]=rs[rt<<1|1]=(r-m);
        }
        add[rt]=-1;
    }
}

void pushup( int rt,int l, int r)
{
    ls[rt] = ls[rt<<1];
    rs[rt] = rs[rt<<1|1];
    sum[rt] = max(max(sum[rt<<1],sum[rt<<1|1]),rs[rt<<1]+ls[rt<<1|1]);
    if(ls[rt<<1]==m-l+1)   //如果左子树的全为空闲连续空间,那么树的左连续空间应加上右子树的左连续空间
        ls[rt]+=ls[rt<<1|1];
    if(rs[rt<<1|1]==r-m)   //同上
        rs[rt]+=rs[rt<<1];
//    printf("%d %d %d %d %d %d %d %d\n",rt,l,r,rs[rt<<1|1],rs[rt<<1],sum[rt],ls[rt],rs[rt]);
}

void build(int rt,int l,int r)
{
    if(l==r)//在最底层也就是区间只有一个点的时候赋初值,通过递归把整个树附上初值
        sum[rt]=ls[rt]=rs[rt]=1;
    else
    {
        build(lson);  //递归
        build(rson);
        pushup(rt,l,r);  //更新父节点
    }
}
//这里返回的是符合条件的连续区间的最左边端点
int query(int rt,int l,int r,int x)  //初始lr是1和n,LR是区间所以会进入else里,最后返回的是一个凑的值
{
    if(l==r)
        return l;
    pushdown(rt,l,r);
    if(sum[rt<<1]>=x)  //如果左子树的最大连续区间满足条件
        query(lson,x);
    else if(rs[rt<<1]+ls[rt<<1|1]>=x)   //如果左子树的右连续区间+右子树的左连续区间满足要求,直接返回该连续区间的左端点
        return m-rs[rt<<1]+1;
    else if(sum[rt<<1|1]>=x) //如果右子树的最大连续区间符合要求
        query(rson,x);
}
void update(int rt,int l,int r,int L,int R,int z)  //更新,这个函数就是为了查找到最低层然后更新
{
    if(L<=l&&r<=R)
    {
        add[rt]=z;
        if(z==1)
            sum[rt]=ls[rt]=rs[rt]=0;
        else
            sum[rt]=rs[rt]=ls[rt]=(r-l+1);
        return;
    }
    pushdown(rt,l,r);
    if(L<=m)
        update(lson,L,R,z);
    if(R>m)
        update(rson,L,R,z);
    pushup(rt,l,r);
}
int main()
{
    int n,t,q,a,b,c;
    scanf("%d",&t);
    for(int op=1; op<=t; op++)
    {
        scanf("%d",&n);
        memset(add,-1,sizeof(add));
        build(1,1,n);
        scanf("%d",&q);
        while(q--)
        {
            scanf("%d",&a);
            if(a==1)
            {
                scanf("%d",&b);
                if(sum[1]>=b)  //先判断
                {
                    int x=query(1,1,n,b);
                    printf("%d\n",x);
                    update(1,1,n,x,x+b-1,1);
                }
                else
                    printf("-1\n");

            }
            else
            {
                scanf("%d %d",&b,&c);
                update(1,1,n,b,c,a);
            }
        }
    }
}

 

扫描线

求面积和

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<queue>
#include<algorithm>
#include<map>
#include<iomanip>
#define INF 99999999
using namespace std;
#define mid ((l+r)>>1)
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
const int MAX=2000+10;
int mark[MAX<<2];//记录某个区间的下底边个数
double sum[MAX<<2];//记录某个区间的下底边总长度
double sum1[MAX<<2];//记录出现至少两次的区间下底边长度
double h[MAX];//对x进行离散化,否则x为浮点数且很大无法进行线段树

//以横坐标作为线段(区间),对横坐标线段进行扫描
//扫描的作用是每次更新下底边总长度和下底边个数,增加新面积
struct seg //线段
{
    double ll,rr,h;
    int d;
    seg() {}
    seg(double x1,double x2,double H,int c):ll(x1),rr(x2),h(H),d(c) {}
    bool operator<(const seg &a)const
    {
        return h<a.h;
    }
} s[MAX];

void pushup(int rt,int l,int r)
{
    if(mark[rt])
        sum[rt]=h[r+1]-h[l];//表示该区间整个线段长度可以作为底边
    else if(l == r)
        sum[rt]=0;//叶子结点则底边长度为0(区间内线段长度为0)
    else
        sum[rt]=sum[rt<<1]+sum[rt<<1|1];
    if(mark[rt]>1)                          //if else 顺序错了会wa
        sum1[rt]=h[r+1]-h[l];//表示该区间整个线段长度可以作为底边
    else if(l == r)
        sum1[rt]=0;//叶子结点则底边长度为0(区间内线段长度为0)
    else if(mark[rt]==1)
        sum1[rt]=sum[rt<<1]+sum[rt<<1|1];

    else
        sum1[rt]=sum1[rt<<1]+sum1[rt<<1|1];
}

void update(int rt,int l,int r,int L,int R,int d)
{
    if(L<=l && r<=R) //该区间是当前扫描线段的一部分,则该区间下底边总长以及上下底边个数差更新
    {
        mark[rt]+=d;//更新底边相差差个数
        pushup(rt,l,r);//更新底边长
        return;
    }
    if(L<=mid)
        update(lson,L,R,d);
    if(R>mid)
        update(rson,L,R,d);
    pushup(rt,l,r);
}

int query(double key,double* x,int n)
{
    int l=0,r=n-1;
    while(l<=r)
    {
        int m=(l+r)>>1;
        if(x[m] == key)
            return m;
        if(x[m]>key)
            r=m-1;
        else
            l=m+1;
    }
    return -1;
}

int main()
{
    int n,num=0,t;
    double x1,x2,y1,y2,z1,z2;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        int k=0;
        for(int i=0; i<n; ++i)
        {
            scanf("%lf %lf %lf %lf",&x1,&y1,&x2,&y2);
            h[k]=x1;
            s[k++]=seg(x1,x2,y1,1); //底边
            h[k]=x2;
            s[k++]=seg(x1,x2,y2,-1);
        }
        sort(h,h+k);
        sort(s,s+k);
        int m=1;
        for(int i=1; i<k; ++i) //去重复端点
            if(h[i] != h[i-1])
                h[m++]=h[i];
        double ans=0,ans1=0;  //ans1是至少出现两次的区间面积,ans是区间面积
        memset(sum,0,sizeof (sum));//如果下面是i<k-1则要初始化,因为如果对第k-1条线段扫描时会使得mark,sum为0才不用初始化的
        memset(sum1,0,sizeof(sum1));
        for(int i=0; i<k; ++i) //扫描线段
        {
            int L=query(s[i].ll,h,m);
            int R=query(s[i].rr,h,m)-1;
            update(1,0,m-1,L,R,s[i].d);//扫描线段时更新底边长度和底边相差个数
            ans=sum[1]*(s[i+1].h-s[i].h);
            ans1+=sum1[1]*(s[i+1].h-s[i].h);  //新增加的至少出现两次区间的面积
        }
        printf("%.2lf %.2lf\n",ans,ans1);
    }
    return 0;
}
/*
这里注意下扫描线段时r-1:int R=query(s[i].l,h,m)-1;
计算底边长时r+1:if(mark[n])sum[n]=h[r+1]-h[l];
解释:假设现在有一个线段左端点是l=0,右端点是r=m-1
则我们去更新的时候,会算到sum[1]=h[mid]-h[l]+h[r]-h[mid+1]
这样的到的底边长sum是错误的,why?因为少算了mid~mid+1的距离,由于我们这利用了
离散化且区间表示线段,所以mid~mid+1之间是有长度的,比如hash[3]=1.2,h[4]=5.6,mid=3
所以这里用r-1,r+1就很好理解了
*/

扫描线求周长

sum[1] 保存的是当前边长度,当前的边与上一次的边的绝对值就是这次增加的边长度,根据这个定理可以求出横线的周长

竖线当作横线处理

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<queue>
#include<cmath>
#include<algorithm>
#include<map>
#include<iomanip>
#define INF 99999999
using namespace std;
#define mid ((l+r)>>1)
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
const int MAX=2010;
int mark[MAX<<2];//记录某个区间的下底边个数
double sum[MAX<<2];//记录某个区间的下底边总长度
double h[MAX];//对x进行离散化,否则x为浮点数且很大无法进行线段树
double hh[MAX];

//以横坐标作为线段(区间),对横坐标线段进行扫描
//扫描的作用是每次更新下底边总长度和下底边个数,增加新周长
struct seg //线段
{
    double ll,rr,h;
    int d;
    seg() {}
    seg(double x1,double x2,double H,int c):ll(x1),rr(x2),h(H),d(c) {}
    bool operator<(const seg &a)const
    {
        return h<a.h;
    }
} s[MAX],ss[MAX];

void pushup(int rt,int l,int r,double* x)
{
    if(mark[rt])
        sum[rt]=x[r+1]-x[l];//表示该区间整个线段长度可以作为底边
    else if(l == r)
        sum[rt]=0;//叶子结点则底边长度为0(区间内线段长度为0)
    else
        sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void update(int rt,int l,int r,int L,int R,int d,double* x)
{
//    printf("%d %d %d^\n",l,r,mid);
    if(L<=l && r<=R) //该区间是当前扫描线段的一部分,则该区间下底边总长以及上下底边个数差更新
    {
        mark[rt]+=d;//更新底边相差差个数
        pushup(rt,l,r,x);//更新底边长
        return;
    }
    if(L<=mid)
        update(lson,L,R,d,x);
    if(R>mid)
        update(rson,L,R,d,x);
    pushup(rt,l,r,x);
}
int er(double key,double* x,int n)
{
    int l=0,r=n-1;
    while(l<=r)
    {
        int m=(l+r)>>1;
        if(x[m] == key)
            return m;
        if(x[m]>key)
            r=m-1;
        else
            l=m+1;
    }
    return -1;
}

int main()
{
    int n,t;
    double x1,x2,y1,y2;
    cin>>t;
    while(t--)
    {
        memset(sum,0,sizeof(sum));
        cin>>n;
        int k=0,kk=0;
        for(int i=0; i<n; ++i)
        {
            cin>>x1>>y1>>x2>>y2;
            h[k]=x1;//横线
            s[k++]=seg(x1,x2,y1,1); 
            h[k]=x2;
            s[k++]=seg(x1,x2,y2,-1);
            hh[kk]=y1;  //竖线
            ss[kk++]=seg(y1,y2,x1,1);
            hh[kk]=y2;
            ss[kk++]=seg(y1,y2,x2,-1);
        }
        sort(h,h+k);
        sort(s,s+k);
        int m=1;
        for(int i=1; i<k; ++i) //去重复端点
            if(h[i] != h[i-1])
                h[m++]=h[i];
        double ans=0;
        double num=0;
        for(int i=0; i<k; ++i) //扫描线段
        {
            int L=er(s[i].ll,h,m);
            int R=er(s[i].rr,h,m)-1;
            update(1,0,m-1,L,R,s[i].d,h);//扫描线段时更新底边长度和底边相差个数
            ans+=fabs(num-sum[1]);//新增加周长
            num=sum[1];
        }
        
        //竖线
        memset(sum,0,sizeof(sum));
        sort(hh,hh+kk);
        sort(ss,ss+kk);
        int mm=1;
        for(int i=1; i<kk; ++i) //去重复端点
            if(hh[i] != hh[i-1])
                hh[mm++]=hh[i];
        num=0;
        memset(mark,0,sizeof mark);
        memset(sum,0,sizeof(sum));//如果下面是i<k-1则要初始化,因为如果对第k-1条线段扫描时会使得mark,sum为0才不用初始化的
        for(int i=0; i<kk; ++i) //扫描线段
        {
            int L=er(ss[i].ll,hh,mm);
            int R=er(ss[i].rr,hh,mm)-1;
            update(1,0,mm-1,L,R,ss[i].d,hh);//扫描线段时更新底边长度和底边相差个数
            ans+=fabs(sum[1]-num);//新增加周长
            num=sum[1];
        }
        printf("%.0lf\n",ans);

    }
    return 0;
}
/*
这里注意下扫描线段时r-1:int R=er(s[i].l,h,m)-1;
计算底边长时r+1:if(mark[n])sum[n]=h[r+1]-h[l];
解释:假设现在有一个线段左端点是l=0,右端点是r=m-1
则我们去更新的时候,会算到sum[1]=h[mid]-h[l]+h[r]-h[mid+1]
这样的到的底边长sum是错误的,why?因为少算了mid~mid+1的距离,由于我们这利用了
离散化且区间表示线段,所以mid~mid+1之间是有长度的,比如hash[3]=1.2,h[4]=5.6,mid=3
所以这里用r-1,r+1就很好理解了
*/

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值