[COCI2011-2012#6] PASTELE

[COCI2011-2012#6] PASTELE

题意:

  • 这份礼物共包含 \(n\) 支蜡笔。每只蜡笔的颜色由色光三原色组成:红、绿、蓝。分别用参数 \(R_i,G_i,B_i\) 表示。这只蜡笔的颜色就由这三个参数来决定。
  • -对于两支蜡笔 i,ji,j,我们定义它们之间的差异值为: \(\max(|R_i-R_j|,|G_i-G_j|,|B_i-B_j|)\)。定义一些蜡笔的色彩值为这些蜡笔中任意两支蜡笔差异值的最大值。
  • 给出这 \(n\) 支蜡笔的特征值,请找出 \(k\) 支蜡笔,使得色彩值最小。

​ 第一眼看到这一题可能并没有什么思路。我们注意到 \(R_i,G_i,B_i\) 的值域范围非常的小。所以我们可以从 \(R_i,G_i,B_i\) 的值域入手。我们想到一个最暴力的方法:枚举答案,再枚举 \(R,G,B\) 的下界。这样我们就可以得到 \(R,G,B\) 的上界。再通过对\(N\)支彩笔的遍历我们可以很轻松的检验此时我们能取到的笔的数量是否大于\(k\)。对于这种方法,我们有一个很常规的优化:二分

​ 通过二分我们可以得到一个更优秀的复杂度:

\[O(255\times255\times255\times n \times log(255)) \]

​ 但是这个复杂度明显过不了这个题目。我们得进一步优化。联想到之前解决这种单个元素具有多个属性的题目的解法,我们可以通过排序来进行优化

首先 \(n\) 个彩笔按照 \(R\) 的数值进行排序,那么我们在枚举时只需要枚举 \(G\),\(B\) 的下界,而在检验答案时,我们从左到右将符合条件的元素添加进一个队列里,由于我们已经通过排序使 \(n\) 个彩笔的 \(R\) 值有序,那么队尾的元素的 \(R\) 值一定是最大的,而队首的元素的 \(R\) 值一定是最小的。我们每添加进一个符合条件的元素在队尾,如果队首队尾元素的 \(R\) 值的差大于我们枚举的答案 \(x\) ,那么就不断弹出队首直至队首队尾元素的 \(R\) 值的差小于我们枚举的答案 \(x\) ,然后统计当前队列内元素的个数,如果个数大于 \(k\) 那么返回 true值。由于 \(n\) 个彩笔的 \(R\) 值是单调递增的,我们可以通过这种方法得到满足条件的每一种选法。此时我们将复杂度降至:

\[O(255\times255\times \log(255) \times n \;+\; n\times \log(n) ) \]

此时我们可以拿到 60pts ,代码如下:

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5+5;
int n,k1;
struct pen
{
    int val[4];
    bool operator < (const pen &x)const
    {
        return val[1]<x.val[1];
    }
}a[MAXN];
queue <int> q;
bool judge(int k,int i,int j,int x)
{
    if(a[k].val[2]>=i&&a[k].val[2]<=i+x&&a[k].val[3]>=j&&a[k].val[3]<=j+x) return true;
    return false;
}
bool check(int x)
{
    int ans=0;
    for(int i=0;i<=255;++i)
    {
        for(int j=0;j<=255;++j)
        {
            int cnt=0;
            while(!q.empty()) q.pop();
            for(int k=1;k<=n;++k)
            {
                if(q.empty()&&judge(k,i,j,x))
                {
                    ++cnt;
                    q.push(k);
                }
                else if(judge(k,i,j,x))
                {
                    q.push(k);
                    while(a[k].val[1]-a[q.front()].val[1]>x) q.pop(),--cnt;
                    ++cnt;
                }
                ans=max(ans,cnt);
            }
        }
    }
    return ans>=k1;
}
void print(int x)
{
    for(int i=0;i<=255;++i)
    {
        for(int j=0;j<=255;++j)
        {
            int cnt=0;
            while(!q.empty()) q.pop();
            for(int k=1;k<=n;++k)
            {
                if(q.empty()&&judge(k,i,j,x))
                {
                    ++cnt;
                    q.push(k);
                }
                else if(judge(k,i,j,x))
                {
                    q.push(k);
                    while(a[k].val[1]-a[q.front()].val[1]>x) --cnt,q.pop();
                    ++cnt;
                }
                if(cnt>=k1)//再做一遍得到符合题意的三个属性上下界。
                {
                    cnt=k1;
                    while(cnt--)
                    {
                        printf("%d %d %d\n",a[q.front()].val[1],a[q.front()].val[2],a[q.front()].val[3]);//输出队列里的元素。
                        q.pop();
                    }
                    return ;
                }
            }
        }
    }
}
int main()
{
    scanf("%d %d",&n,&k1);
    for(int i=1;i<=n;++i)
        scanf("%d %d %d",&a[i].val[1],&a[i].val[2],&a[i].val[3]);
    sort(a+1,a+1+n);
    int l=1,r=255,ans=255;
    while(l<=r)
    {
        int mid=l+r>>1;
        if(check(mid))
        {
            ans=mid;
            r=mid-1;
        }
        else l=mid+1;
    }
    printf("%d\n",ans);
    print(ans);
    return 0;
}

​ 显然, \(n\) 的数值太大了,我们必须想办法将 \(n\) 优化掉。我们可以发现我们 check 函数里面实际上重复将 \(n\) 个元素装进队列里多次。那么我们尝试可以将元素塞入 \(\text{vector}\) 中。同样的,

​ 我们以 \(R\) 值排序,排序后,再一个个塞入以 \(G\),\(B\) 的值为下标的 \(\text{vector}\) 中。在 check 中我们同样枚举三个属性的下界,并通过二分出的答案得到三个属性的上界。我们可以先枚举 \(R\) 的上下界,然后接下来两个属性我们已经将他们当做下标,所以想象一个二维平面,对于一个点 \((x,y)\) 我们赋予它一个权值 \(w\)\(w\)\(R\) 值在枚举的上下界范围内,\(G\)\(x\)\(B\)\(y\) 的元素个数。这个我们可以在 \(\text{vector}[x][y]\) 二分上下界的位置并相减得到元素个数,但这样效率太低。我们考虑从小到大枚举 \(R\) 值的下界,由于 \(\text{vector}\) 中的元素 \(R\) 值是有序的,我们用一个队列来维护 \(\text{vector}[x][y]\) 中符合上下界元素的个数。具体可以见代码。

​ 这样操作后我们会发现,我们需要判断是否有一个 \(x\times x\) 大小的矩阵中所有点的权值 \(w\) 之和大于等于 \(k\)。我们可以用二维前缀和来解决这样的问题。

​ 同样的,上文中的方案是再算出符合答案的三个属性的上下界进行输出,这样有些慢,我们考虑开一个数组记录每次二分 check 时得到的三个属性的上下界,而在输出方案时我们遍历 \(n\) 个彩笔,符合条件的就输出。相当于我们使用了一个以空间来换时间的策略。

​ 此时,我们将复杂度优化为

\[O(255\times255\times255\times \log(255)+\;n\;+\:n\times \log(n)) \]

​ 这个复杂度可以通过这道题目,代码如下:

#include<bits/stdc++.h>
#define R register
using namespace std;
const int MAXN = 1e5+5;
int n,k1,lim;
struct pen
{
    int val[4];
}a[MAXN];
vector <int> vec[256][256];
int cnt[256][256],head[256][256],tail[256][256],ans[256][3],sum[256][256];
inline int read(){
   int s=0,w=1;
   char ch=getchar();
   while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
   while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
   return s*w;
}
bool check(int x)
{
    memset(head,0,sizeof head);
    memset(tail,0,sizeof tail);
    for(R int i=0;i<=lim;++i)
    {
        for(R int j=0;j<=lim;++j)
        {
            for(R int k=0;k<=lim;++k)
            {
                while(head[j][k]<vec[j][k].size()&&vec[j][k][head[j][k]]<i) ++head[j][k];
                while(tail[j][k]<vec[j][k].size()&&vec[j][k][tail[j][k]]<=i+x) ++tail[j][k];//队列维护。
                cnt[j][k]=tail[j][k]-head[j][k];
                bool flag=0;
                sum[j][k]=(j?sum[j-1][k]:0)+(k?sum[j][k-1]:0)+cnt[j][k]-(j&&k?sum[j-1][k-1]:0);//二维前缀和。
                if(j>x&&k>x)
                    if(sum[j][k]-sum[j-x-1][k]-sum[j][k-x-1]+sum[j-x-1][k-x-1]>=k1) flag=true;
                else if(j>x&&k<=x)
                    if(sum[j][k]-sum[j-x-1][k]>=k1) flag=true;
                else if(j<=x&&k>x)
                    if(sum[j][k]-sum[j][k-x-1]>=k1) flag=true;
                else if(j<=x&&k<=x)
                    if(sum[j][k]>=k1) flag=true;
                if(flag)
                {
                    ans[x][0]=i,ans[x][1]=j,ans[x][2]=k;//记录此时三个属性的上下界。
                    return true;
                }
            }
        }
    }
    return false;
}
inline int Max(int a,int b,int c,int d)
{
    return max(max(a,b),max(c,d));
}
void print(int x)
{
    int a1,b,c;
    a1=ans[x][0],b=ans[x][1],c=ans[x][2];
    int cnt=0;
    for(R int i=1;i<=n;++i)
    {
        if(a[i].val[1]>=a1&&a[i].val[1]<=x+a1)
        {
            if(a[i].val[2]>=b-x&&a[i].val[2]<=b)
            {
                if(a[i].val[3]>=c-x&&a[i].val[3]<=c)
                {
                    ++cnt;
                    printf("%d %d %d\n",a[i].val[1],a[i].val[2],a[i].val[3]);
                    if(cnt==k1) return ;
                }
            }
        }
    }
}
bool cmp(pen a,pen b)
{
    return a.val[1]<b.val[1];
}
int main()
{
    n=read(),k1=read();
    for(R int i=1;i<=n;++i)
    {
        a[i].val[1]=read();
        a[i].val[2]=read();
        a[i].val[3]=read();
    }
    sort(a+1,a+1+n,cmp);
    lim=255;
    for(R int i=1;i<=n;++i)
        vec[a[i].val[2]][a[i].val[3]].push_back(a[i].val[1]);
    int l=1,r=lim,ans=lim;
    while(l<=r)
    {
        int mid=l+r>>1;
        if(check(mid))
        {
            ans=mid;
            r=mid-1;
        }
        else l=mid+1;
    }
    printf("%d\n",ans);
    print(ans);
    return 0;
}

​ 这样我们就通过了这道题。其实我们可以优化掉 \(\text{vector}\) 采取三维前缀和的方式。check 就查找有没有一个边长为 \(x\) 的立方体中所有点的权值之和大于 \(k\) 。这种方法这里就不在赘述了。(其实是我也不会)

posted @ 2021-06-25 15:58  夜空之星  阅读(128)  评论(0编辑  收藏  举报