本文最后更新于:2020年4月19日 晚上

最近做了几道高维偏序的简单题。

对于这类题目一般有三种通用方法,一是cdq分治,维数越多嵌套次数就越多,总体来说思路就是对时间分治,每次统计出左半区间的贡献点对右半区间的询问点的贡献,这样对于每个询问点来说,它前面的所有对它造成影响的点就都会被算到。二是用bitset优化暴力,应该只能处理计数问题,但优点是好写,适合做高维偏序。三是KDtree,emmmm,这个没怎么写过,多校训练十分艰难的写过一次,通过了90%的数据然后TLE了。

1.LOJ#112 三维偏序

计数题,对其中一维排序后,cdq分治套树状数组,时间复杂度O(nlog2n)O(nlog^{2}n)

2.[COGS 2479HZOI 2016] 偏序

计数题,四维偏序,cdq分治套cdq分治套树状数组,时间复杂度O(nlog3n)O(nlog^{3}n)

怎么理解这个分治套分治呢。假设属性为a,b,c,第一次分治的时候我们将区间[l,mid]的点标记成贡献点,[mid+1,r]的点标记成询问点,然后区间[l,r]按a属性排序,消去a的影响,这样问题就变成了有n次操作,每次要么插入一个点(b,c),要么查询之前插入的点中有多少点在(b,c)左下方,这样问题就降了一维化成了三维偏序了。

3.[COGS 2580 HZOI 2015]偏序 II

计数题,五维偏序,在上一题的基础上外面再套一次cdq分治,时间复杂度十分感人O(nlog4n)O(nlog^{4}n)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define N 100010
struct node
{
    int w,x,y,z;
    bool t,s;
} a[N],b[N],c[N],d[N];
int n;
ll ans;
struct BIT
{
    int c[N];
    void add(int x,int y)
    {
        for(; x<=n; x+=x&(-x)) c[x]+=y;
    }
    int ask(int x)
    {
        int ans=0;
        for(; x; x-=x&(-x)) ans+=c[x];
        return ans;
    }
} bit;
void cdq3(int l,int r)
{
    if(l==r) return;
    int mid=(l+r)>>1;
    cdq3(l,mid);
    cdq3(mid+1,r);
    int i=l,j=mid+1,k=l;
    while(i<=mid && j<=r)
    {
        if(c[i].y<c[j].y)
        {
            if(!c[i].s && !c[i].t) bit.add(c[i].z,1);
            d[k++]=c[i++];
        }
        else
        {
            if(c[j].s && c[j].t) ans+=bit.ask(c[j].z);
            d[k++]=c[j++];
        }
    }
    while(i<=mid)
    {
        if(!c[i].s && !c[i].t) bit.add(c[i].z,1);
        d[k++]=c[i++];
    }
    while(j<=r)
    {
        if(c[j].s && c[j].t) ans+=bit.ask(c[j].z);
        d[k++]=c[j++];
    }
    for(int i=l; i<=mid; ++i) if(!c[i].s && !c[i].t) bit.add(c[i].z,-1);
    for(int i=l; i<=r; ++i) c[i]=d[i];
}
void cdq2(int l,int r)
{
    if(l==r) return;
    int mid=(l+r)>>1;
    cdq2(l,mid);
    cdq2(mid+1,r);
    int i=l,j=mid+1,k=l;
    while(i<=mid && j<=r)
    {
        if(b[i].x<b[j].x) c[k]=b[i++],c[k++].s=false;
        else c[k]=b[j++],c[k++].s=true;
    }
    while(i<=mid) c[k]=b[i++],c[k++].s=false;
    while(j<=r) c[k]=b[j++],c[k++].s=true;
    for(int i=l; i<=r; ++i) b[i]=c[i];
    cdq3(l,r);
}
void cdq1(int l,int r)
{
    if(l==r) return;
    int mid=(l+r)>>1;
    cdq1(l,mid);
    cdq1(mid+1,r);
    int i=l,j=mid+1,k=l;
    while(i<=mid && j<=r)
    {
        if(a[i].w<a[j].w) b[k]=a[i++],b[k++].t=false;
        else b[k]=a[j++],b[k++].t=true;
    }
    while(i<=mid) b[k]=a[i++],b[k++].t=false;
    while(j<=r) b[k]=a[j++],b[k++].t=true;
    for(int i=l; i<=r; ++i) a[i]=b[i];
    cdq2(l,r);
}
int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        for(int i=1; i<=n; i++)scanf("%d",&a[i].w);
        for(int i=1; i<=n; i++)scanf("%d",&a[i].x);
        for(int i=1; i<=n; i++)scanf("%d",&a[i].y);
        for(int i=1; i<=n; i++)scanf("%d",&a[i].z);
        ans=0;
        cdq1(1,n);
        printf("%lld\n",ans);
    }
}

4.[COGS 2639. HZOI 2015] 偏序++

计数题,七维偏序。只能bitset了,每个元素开一个bitset表示哪些点比它小,一维一维的考虑,从小到大枚举值,然后对每个元素and一下就行了。时间复杂度O(kn2/64)O(kn^2/64)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=40005;
bitset<N> bs[N],tmp;
int n,k,ans;
int pos[N],a[N];
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;++i) bs[i]=bs[i-1],bs[i][i-1]=1;
    while(k--)
    {
        for(int i=1;i<=n;++i) scanf("%d",a+i),pos[a[i]]=i;
        tmp.reset();
        for(int i=1;i<=n;++i) bs[pos[i]]&=tmp,tmp[pos[i]]=1;
    }
    ll ans=0;
    for(int i=1;i<=n;++i) ans+=bs[i].count();
    printf("%lld\n",ans);
}

5.SPOJ LIS2 Another Longest Increasing Subsequence Problem

求最优解,即最长上升子序列的长度,每个元素有两个属性。回忆一下一个属性的话,我们可以用树状数组求前缀最大值的方法优化dp的转移,再加一维就用cdq分治套一下就行了。

与之前计数题不同的地方在于,计数题中贡献对询问的影响是独立的,没有先后顺序的问题,但是做这个dp的过程就必须是从前往后了,比如说序列1,2,3,4,如果用3去更新4的话,3的dp值就必须在之前就已经算出来是3。稍微改一下分治的顺序就可以了,先递归到[l,mid],然后用左半区间更新右半区间的dp值,然后递归到右半区间。

6.2018 牛客网暑期ACM多校训练营(第九场)Longest Common Subsequence

求四个序列的最长公共子序列,保证值范围1-n,前三个序列中每一种值出现次数不超过2次。

先考虑四个序列都是排列的情况,假设对于值x来说pa[x],pb[x],pc[x],pd[x]在四个序列中的位置,那么最长公共子序列就转化成了求四维偏序的最长链。对于这个题目我们可以2的3次方枚举每个值的位置三元组,这样总点数就是8n的,还是求四维偏序的最长链。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=80005;
int n;
int dp[N],vis[N];
int pos[N][3][2];
struct node
{
    int x,y,z,t,o;
    void show()
    {
        printf("%d:(%d,%d,%d)  %d\n",o,x,y,z,dp[o]);
    }
} a[N],b[N],c[N];


bool cmpx(const node &a,const node &b)
{
    if(a.x!=b.x) return a.x<b.x;
    return a.y>b.y;
}
bool cmpy(const node &a,const node &b)
{
    if(a.y!=b.y) return a.y<b.y;
    return a.z>b.z;
}
struct BIT
{
    int c[N];
    void change(int x,int y)
    {
        for(; x<=n; x+=x&(-x)) c[x]=max(c[x],y);
    }
    void clear(int x)
    {
        for(; x<=n; x+=x&(-x)) c[x]=0;
    }
    int ask(int x)
    {
        int ans=0;
        for(; x; x-=x&(-x)) ans=max(ans,c[x]);
        return ans;
    }
} bit;
void cdq2(int l,int r)
{
    if(l==r) return;
    int mid=(l+r)>>1;
    cdq2(l,mid);

    for(int i=l; i<=r; ++i) c[i]=b[i];
    sort(c+l,c+mid+1,cmpy);
    sort(c+mid+1,c+r+1,cmpy);
    int i=l,j=mid+1;
    for(; j<=r; ++j)
    {
        for(; i<=mid && c[i].y<c[j].y; ++i)
        if(c[i].t==0)
            bit.change(c[i].z,dp[c[i].o]);
        if(c[j].t==1)
            dp[c[j].o]=max(dp[c[j].o],bit.ask(c[j].z-1)+1);
    }
    for(i=l; i<=mid; ++i)
        if(c[i].t==0) bit.clear(c[i].z);

    cdq2(mid+1,r);
}
void cdq1(int l,int r)
{
    if(l==r) return;
    int mid=(l+r)>>1;
    cdq1(l,mid);

    for(int i=l; i<=r; ++i) b[i]=a[i],b[i].t=(i>mid);
    sort(b+l,b+r+1,cmpx);
    cdq2(l,r);

    cdq1(mid+1,r);
}
int main()
{
    scanf("%d",&n);
    for(int i=0; i<3; ++i)
    {
        memset(vis,0,sizeof(vis));
        for(int j=1; j<=n; ++j)
        {
            int x;
            scanf("%d",&x);
            if(!vis[x]) pos[x][i][0]=pos[x][i][1]=j,vis[x]=1;
            else pos[x][i][1]=j;
        }
    }
    int cnt=0;
    for(int i=1; i<=n; ++i)
    {
        int x;
        scanf("%d",&x);
        if(!pos[x][0][0]||!pos[x][1][0]||!pos[x][2][0]) continue;
        for(int j=7; j>=0; --j)
        {
            ++cnt;
            a[cnt].o=cnt;
            a[cnt].x=pos[x][0][j&1];
            a[cnt].y=pos[x][1][j>>1&1];
            a[cnt].z=pos[x][2][j>>2&1];
            a[cnt].t=0;
            //a[cnt].show(cnt);
            dp[cnt]=1;
        }
    }

    cdq1(1,cnt);
    int ans=0;
    for(int i=1; i<=cnt; ++i)
        ans=max(ans,dp[i]);
    printf("%d\n",ans);
    return 0;
}
/*
(1,1,2)
(2,2,3)
(3,4,4)
(4,5,5)
*/

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

2017 西安网络赛 A题 TREE 上一篇
偏序集 下一篇