1. 程式人生 > 實用技巧 >題解 P5048 【[Ynoi2019模擬賽]Yuno loves sqrt technology III】

題解 P5048 【[Ynoi2019模擬賽]Yuno loves sqrt technology III】

未經允許,禁止轉載

原文連結

其實本蒟蒻自己想了一會後便有了一些思路,但實現方面還是還是學習了\(\text{dllxl}\)的做法的

強制線上導致一些離線做法沒有辦法解決(如莫隊),我們考慮分塊,預處理出第l到第r個塊的眾數次數,再把零散塊的數一個一個更新答案。

我們用一個vector存某個值的下標序列,設當前答案是ans,如果當前點在左邊,它的後ans+1個元素在r內,則更新答案,右邊則反之。

然而今天我是來教大家卡常的

關於這個題的提交記錄真的卡的我心態爆炸

好像交了一次題解都沒過? 不要封我

好了,正文開始,首先了解一下這個火車頭

這是個好東西,可以讓你\(n^2\)過百萬(霧

但加了這個好像還是T完了?

沒事,我們還有IO優化

下面是我了機房大佬的IO優化

namespace quick {
#define tp template<typename Type>
    namespace in {
        inline char getc() {
            static char buf[1<<21],*p1=buf,*p2=buf;
            return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;
        }
        inline int read(char *s) {
            *s=getc();
            while(isspace(*s)) {*s=getc();if(*s==EOF) return 0;}
            while(!isspace(*s)&&*s!=EOF) {s++;*s=getc();}
            *s='\0'; return 1;
        }
        tp inline int read(Type &x) {
            x=0;bool k=false;char c=getc();
            while(!isdigit(c)) {k|=(c=='-');c=getc();if(c==EOF) return 0;}
            while(isdigit(c)) {x=(x<<1)+(x<<3)+(c^48);c=getc();}
            x*=(k?-1:1); return 1;
        }
        template <typename Type,typename... Args>
        inline int read(Type &t,Args &...args) {
            int res=0;
            res+=read(t);res+=read(args...);
            return res;
        }
    }
    using in::read;
    namespace out {
        char buf[1<<21];int p1=-1;const int p2=(1<<21)-1;
        inline void flush() {
            fwrite(buf,1,p1+1,stdout);
            p1=-1;
        }
        inline void putc(const char &c) {
            if(p1==p2) flush();
            buf[++p1]=c;
        }
        inline void write(char *s) {
            while(*s!='\0') putc(*s),s++;
        }
        tp inline void write(Type x) {
            static char buf[30];int p=-1;
            if(x<0) {putc('-');x=-x;}
            if(x==0) putc('0');
            else for(;x;x/=10) buf[++p]=x%10+48;
            for(;p!=-1;p--) putc(buf[p]);
        }
        inline void write(char c) {putc(c);}
        template <typename Type,typename... Args>
        inline void write(Type t,Args ...args) {
            write(t);write(args...);
        }
    }
    using out::write;
    using out::flush;
    tp inline Type max(const Type a,const Type b) {
        if(a<b) return b;
        return a;
    }
    tp inline Type min(const Type a,const Type b) {
        if(a<b) return a;
        return b;
    }
    tp inline void swap(Type &a,Type &b) {
        a^=b^=a^=b;
    }
    tp inline Type abs(const Type a) {
        return a>=0?a:-a;
    }
#undef tp
}
using namespace quick;

直接read,write就ok了

還有一個很關鍵很關鍵的優化(敲黑板

這是我們在計算第i個塊到第j個塊的程式碼,\(\text{kmx[][]}\)就是那個陣列,tot是塊總數。

    for(int i=1;i<=tot;++i)
    {
        for(int j=i;j<=tot;++j)
        {
            kmx[i][j]=kmx[i][j-1];
            for(int k=kn[j-1]+1;k<=kn[j];++k)
                kmx[i][j]=max(kmx[i][j],++vis[al[k]]);
        }
        memset(vis,0,sizeof(vis));
    }

但我們可以發現第一個塊與最後一個塊根本用不上,所以可以改成

    for(int i=2;i<tot;++i)
    {
        for(int j=i;j<tot;++j)
        {
            kmx[i][j]=kmx[i][j-1];
            for(int k=kn[j-1]+1;k<=kn[j];++k)
                kmx[i][j]=max(kmx[i][j],++vis[al[k]]);
        }
        memset(vis,0,sizeof(vis));
    }

大約可以快0.1s。

還有要用memset,它絕對比其他方式都快(至少這個題是的)。

還有max不要用STL,自己手寫要快一點。

還有lxl是真的dio

完整程式碼

inline int max(const int &a,const int &b)
{
    return a>b?a:b;
}

int main()
{
    int t,pre=0;
    read(n,t);
    for(int i=1;i<=n;++i)
        read(a[i]),b[i]=a[i];
    tot=(n-1)/mxn+1;
    for(int i=1;i<=tot;++i)
        kn[i]=kn[i-1]+tot;
    kn[tot]=n;
    // tot=(n-1)/mxn+1;
    // for(int i=1;i<=tot;++i)
    //     kn[i]=kn[i-1]+mxn;
    // kn[tot]=n;
    for(int i=1;i<=tot;++i)
        for(int j=kn[i-1]+1;j<=kn[i];++j)
            bel[j]=i;
    sort(b+1,b+n+1);
    m=unique(b+1,b+n+1)-b-1;
    for(int i=1;i<=n;++i)
        al[i]=lower_bound(b+1,b+m+1,a[i])-b;
    for(int i=2;i<tot;++i)
    {
        for(int j=i;j<tot;++j)
        {
            kmx[i][j]=kmx[i][j-1];
            for(int k=kn[j-1]+1;k<=kn[j];++k)
                kmx[i][j]=max(kmx[i][j],++vis[al[k]]);
        }
        memset(vis,0,sizeof(vis));
    }
    for(int i=1;i<=n;++i)
    {
        pos[al[i]].push_back(i);
        vps[i]=pos[al[i]].size()-1;
    }
    int l,r;
    while(t--)
    {
        read(l,r);
        l^=pre;r^=pre;
        int pl=bel[l],pr=bel[r];
        if(pl==pr)
        {
            int nowans=0;
            for(int i=l;i<=r;++i)
                nowans=max(nowans,++vis[al[i]]);
            for(int i=l;i<=r;++i)
                vis[al[i]]=0;
            write(nowans,'\n');
            pre=nowans;
        }
        else
        {
            int nowans=kmx[pl+1][pr-1];
            for(int i=l;i<=kn[pl];++i)
            {
                int siz=pos[al[i]].size();
                while(vps[i]+nowans<siz&&pos[al[i]][vps[i]+nowans]<=r) ++nowans;
            }
            for(int i=kn[pr-1]+1;i<=r;++i)
                while(vps[i]-nowans>=0&&pos[al[i]][vps[i]-nowans]>=l) ++nowans;
            write(nowans,'\n');
            pre=nowans;
        }
    }
    return 0;
}

後記

首先我的程式碼不一定在全天候都能過(但你凌晨交是肯定能過),另外如果你認真讀了我的程式碼,你就會發現我用的是固定塊長mxn=708,但我在初始化塊的時候是這麼寫的

    tot=(n-1)/mxn+1;
    for(int i=1;i<=tot;++i)
        kn[i]=kn[i-1]+tot;
    kn[tot]=n;

kn是塊的右端點,實際上應是

    tot=(n-1)/mxn+1;
    for(int i=1;i<=tot;++i)
        kn[i]=kn[i-1]+mxn;
    kn[tot]=n;

但上面的寫法竟然AC了,而且比下面的快,這也算玄學卡常嗎

希望有大佬能解釋

完結撒花!