1. 程式人生 > 其它 >[COCI2011-2012#6] PASTELE

[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|)max(∣Ri−Rj∣,∣Gi−Gj∣,∣Bi−Bj∣)\)。定義一些蠟筆的色彩值為這些蠟筆中任意兩支蠟筆差異值的最大值。
  • 給出這 \(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\) 。這種方法這裡就不在贅述了。(其實是我也不會)