【GDOI2018模拟7.14】小奇的糖果 树状数组+扫描线

168 篇文章 0 订阅
92 篇文章 1 订阅

好像上去讲的有点快,主要是本身就挺短,,这里讲的会详细一点。
题意很明显就不说了。

题意就不说了,注意线段上的点是不能选的。
首先要明确一个东西,这里说是双向链表维护,是因为我们维护了一个点的前驱后继,这符合双向链表的定义,但是实际上大家把他当作普通数组一样维护就好了。

先把横坐标离散化,注意是线性离散的话初值不能是0因为有的坐标可以是0(因为这个所以WA0了)。
首先我们假设我们画的那一条直线在无限低的地方,这时候答案相当于相邻两个同种颜色的中间的最大点数。(注意不包括这种颜色)
怎么求?
把横坐标从小到大排序,维护l[i],r[i]分别表示距离i最近的相同颜色的点的编号。
直接维护比较麻烦,设一个last[i]表示i这种颜色最近一次出现的地方在哪个位置,然后用last维护l,r。
维护的同时维护答案,用树状数组和l,r维护一下区间和就好了。

当然,这是满状态,也就是直线在最下方的时候,然后我们将点按照纵坐标排序,逐渐向上移动,每一次将同一行的点从树状数组删掉,然后将l,r合并。同时维护答案,跟上面的一样,用已经合并了的l,r更新一下就好了。

最后还有一种情况就是枚举一下哪一种颜色不选,然后对ans取个max,注意要在删点之前,也是用树状数组维护。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=1e5+5;
int n,k,ans;
struct point
{
    int x,y,id,col;
}po[N];
int a[N],pos[N],val[N],last[N],Left[N],Right[N];
//int f[2][N][N];
inline int lowbit(int x)
{
    return x&(-x);
}
inline void add(int x,int v)
{
    while (x<=n+1)
    {
        val[x]+=v;
        x+=lowbit(x);
    }
}
inline int ask(int x)
{
    int ret=0;
    while (x>0)
    {
        ret+=val[x];
        x-=lowbit(x);
    }
    return ret;
}
bool cmp1(point a,point b)
{
    return a.x<b.x;
}
bool cmp2(point a,point b)
{
    return a.y<b.y;
}
inline void solve()
{
    memset(val,0,sizeof(val));
    memset(last,0,sizeof(last));
    sort(po+1,po+1+n,cmp1);
    pos[n+1]=n+1;
    fo(i,1,n)add(po[i].x,1);
    fo(i,1,n)
    {
        int l=last[po[i].col];
        Left[po[i].id]=l,Right[po[i].id]=n+1;
        last[po[i].col]=po[i].id;
        if (l)Right[l]=po[i].id;
        if (pos[l]+1<=pos[po[i].id]-1)ans=max(ans,ask(pos[po[i].id]-1)-ask(pos[l]));
    }
    fo(i,1,k)if (pos[last[i]]+1<=n)ans=max(ans,ask(n+1)-ask(pos[last[i]]));
    sort(po+1,po+1+n,cmp2);
    int j=1;
    fo(i,1,n)
    {
        while (j<=n&&po[i].y==po[j].y)add(po[j].x,-1),j++;
        Left[Right[po[i].id]]=Left[po[i].id];
        Right[Left[po[i].id]]=Right[po[i].id];
        if (pos[Right[po[i].id]]-1>=pos[Left[po[i].id]]+1)
            ans=max(ans,ask(pos[Right[po[i].id]]-1)-ask(pos[Left[po[i].id]]));
    }

}
int main()
{
    int cas=0;
    scanf("%d",&cas);
    while (cas--)
    {
        scanf("%d%d",&n,&k);
        fo(i,1,n)
        {
            scanf("%d%d%d",&po[i].x,&po[i].y,&po[i].col);
            a[i]=po[i].x;
            po[i].id=i;
            //fo(j,1,n)
            //f[0][i][j]=abs(po[i].x-po[j].x),f[1][i][j]=abs(po[i].y-po[j].y);
        }
        sort(a+1,a+1+n);
        int tot=0;
        ans=0;
        fo(i,1,n)
        po[i].x=pos[i]=lower_bound(a+1,a+1+n,po[i].x)-a;
        solve();
        fo(i,1,n)po[i].y=-po[i].y;
        solve();
        printf("%d\n",ans);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值