BZOJ 2253 纸箱堆叠(CDQ分治)

26 篇文章 0 订阅

Description
P 工厂是一个生产纸箱的工厂。纸箱生产线在人工输入三个参数 n p a , , 之后,即可自动化生产三边边长为
(a mod P,a^2 mod p,a^3 mod P)
(a^4 mod p,a^5 mod p,a^6 mod P)
….
(a^(3n-2) mod p,a^(3n-1) mod p,a^(3n) mod p)
的n个纸箱。在运输这些纸箱时,为了节约空间,必须将它们嵌套堆叠起来。一个纸箱可以嵌套堆叠进另一个纸箱当且仅当它的最短边、次短边和最长边长度分别严格小于另一个纸箱的最短边、次短边和最长边长度。这里不考虑任何旋转后在对角线方向的嵌套堆叠。你的任务是找出这n个纸箱中数量最多的一个子集,使得它们两两之间都可嵌套堆叠起来。
Input
输入文件的第一行三个整数,分别代表 a,p,n
Output
输出文件仅包含一个整数,代表数量最多的可嵌套堆叠起来的纸箱的个数
Sample Input
10 17 4
Sample Output
2
Solution
三维LIS问题,CDQ分治,按第一维x排序,由于此处是严格上升,故在分治过程中mid值需要使得[l,,mid]的第一维严格小于[mid+1,r]的第一维,以dp[i]表示以第i个元素结尾的LIS长度,则有dp[i]=max(dp[i],dp[j]+1),其中mid+1<=i<=r,l<=j<=mid,yj < yi,zj < zi,如果对[l,mid]和[mid+1,r]都按y为第一关键字,z为第二关键字升序排,每次计算dp[i]之前将所有满足yj < yi的j都以zj为下标,dp[j]为键值插入到树状数组中,那么问题转化为求树状数组中下标小于zi的最大值,最后的答案为max(dp[i])
Code

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
#define maxn 111111
int A,P,n,t[maxn],tot;
struct node
{
    int x,y,z,ans;
    void init(int a,int b,int c)
    {
        if(a>b)swap(a,b);
        if(a>c)swap(a,c);
        if(b>c)swap(b,c);
        x=a,y=b,t[++tot]=z=c,ans=1;
    }
}p[maxn];
int cmpx(node a,node b)
{
    if(a.x!=b.x)return a.x<b.x;
    if(a.y!=b.y)return a.y<b.y;
    return a.z<b.z;
}
int cmpy(node a,node b)
{
    if(a.y!=b.y)return a.y<b.y;
    return a.z<b.z;
}
struct BIT 
{
    #define lowbit(x) (x&(-x))
    int b[maxn];
    void init()
    {
        memset(b,0,sizeof(b));
    }
    void update(int x,int v)
    {
        while(x<=n)
        {
            b[x]=max(b[x],v);
            x+=lowbit(x);
        }
    }
    int query(int x)
    {
        int ans=0;
        while(x)
        {
            ans=max(ans,b[x]);
            x-=lowbit(x);
        }
        return ans;
    }
    void clear(int x)
    {
        while(x<=n)
        {
            b[x]=0;
            x+=lowbit(x);
        }
    }
}bit;
void CDQ(int l,int r)
{
    if(l>=r)return ;
    //保证[l,mid]的第一维严格小于[mid+1,r]的第一维 
    int mid=-1,tl=(l+r)>>1,tr=tl+1;
    while(tl>=l||tr<=r)
    {
        if(tl>=l&&p[tl].x!=p[tl+1].x)
        {
            mid=tl;
            break;
        }
        if(tr<=r&&p[tr-1].x!=p[tr].x)
        {
            mid=tr-1;
            break;
        }
        tl--,tr++;
    }
    if(mid==-1)return ;
    CDQ(l,mid);
    sort(p+l,p+mid+1,cmpy);
    sort(p+mid+1,p+r+1,cmpy);
    for(int i=mid+1,j=l;i<=r;i++)
    {   
        for(;j<=mid&&p[j].y<p[i].y;j++)
            bit.update(p[j].z,p[j].ans);
        p[i].ans=max(p[i].ans,bit.query(p[i].z-1)+1);
    }
    for(int i=l;i<=mid;i++)bit.clear(p[i].z);
    sort(p+mid+1,p+r+1,cmpx);
    CDQ(mid+1,r);
}
int main()
{
    scanf("%d%d%d",&A,&P,&n);
    tot=0,bit.init();
    int x,y,z,temp=1;
    for(int i=1;i<=n;i++)
    {
        x=temp=1ll*A*temp%P;
        y=temp=1ll*A*temp%P;
        z=temp=1ll*A*temp%P;
        p[i].init(x,y,z);
    }
    sort(p+1,p+n+1,cmpx);
    sort(t+1,t+tot+1);
    for(int i=1;i<=n;i++)
        p[i].z=lower_bound(t+1,t+tot+1,p[i].z)-t;
    CDQ(1,n);
    int ans=1;
    for(int i=1;i<=n;i++)ans=max(ans,p[i].ans);
    printf("%d\n",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值