1. 程式人生 > >P4135 作詩

P4135 作詩

pro onclick 增加 display namespace 更新 -s spl 細節

傳送門

分塊

設sum[ i ] [ j ] 存從左邊到第 i 塊時,數字 j 的出現次數

 f [ i ] [ j ] 存從第 i 塊,到第 j 塊的一整段的答案

那麽最後答案就是一段區間中幾塊整段的答案加上兩邊小段的貢獻

考慮兩邊小段的影響,對於每一個出現的數

它可能會使答案增加(使原本大區間中出現奇數次的數變成出現偶數次)

也可能使答案減少(使原本大區間中出現偶數次的數變成出現奇數次)

有了 sum 數組我們可以很方便地求出大區間中每個數的出現次數

對小段的每個數直接計算一下它對答案的貢獻

開一個 cnt[ i ] 記錄一下之前每個數 i 出現的次數就好了

技術分享圖片
//
bl,br是大區間的左右邊界的塊 ans=f[bl][br];//ans初值為大區間的答案 for(int i=l;i<L[bl];i++)//L[i]存第i塊的左端點 { cnt[a[i]]++;//記錄a[i]在小段出現的次數 t=cnt[a[i]]+sum[br][a[i]]-sum[bl-1/*註意是bl-1*/][a[i]];//計算此時a[i]出現的次數 if(!(t&1)) ans++;//如果a[i]出現的次數變成了偶數次,答案就加一 else if(t>1) ans--;//否則如果出現次數超過2次且使出現次數變成奇數次,答案減1
//要特殊考慮t=1的情況,t=1時不會對答案有影響,因為t=0時不算出現偶數次 } for(int i=L[br+1];i<=r;i++)//註意i的範圍 { cnt[a[i]]++; t=cnt[a[i]]+sum[br][a[i]]-sum[bl-1][a[i]]; if(!(t&1)) ans++; else if(t>1) ans--; } for(int i=l;i<L[bl];i++) cnt[a[i]]--; for(int i=L[br+1];i<=r;i++) cnt[a[i]]--;//別忘記還原cnt,以後詢問還要用,註意不要用memset
關於詢問

然後來考慮如何預處理

sum數組很好求,關鍵是 f

好像枚舉每個塊復雜度會爆炸...

我們來看看處理詢問時的方法,對每個數都計算貢獻

預處理就相當於詢問每兩個塊之間的貢獻

我們可以用同樣的方法,cnt[ i ] 記錄 i 出現了幾次

從左到右掃,計算每個數對答案的貢獻,如果掃到一個區間的右端點了,就記錄一波 f

技術分享圖片
//bel[i]表示點i屬於第幾個塊
for(int i=1;i<=bel[n];i++)//枚舉每個左塊
{
    t=0;
    for(int j=L[i];j<=n;j++)//考慮右邊所有塊
    {
        cnt[a[j]]++;//記錄
        if(!(cnt[a[j]]&1)) t++;
        else if(cnt[a[j]]>1) t--;
        //考慮對答案的貢獻
        if(bel[j]!=bel[j+1]) f[i][bel[j]]=t;//如果到了一個區間的右端點,更新f
    }
    for(int j=L[i];j<=n;j++) cnt[a[j]]--;//別忘了還原cnt
}
關於預處理

這樣我們就可以在 O( n*sqrt(n) )的時間內預處理,在 O( sqrt(n)+2*sqrt(n) ) 的時間處理詢問

註意一下常數就輕松過了

細節很多的一題

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<0||ch>9)
    {
        if(ch==-) f=-1;
        ch=getchar();
    }
    while(ch>=0&&ch<=9)
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x*f;
}
const int N=1e5+7,M=327;
int n,m,q,ans;
int a[N],bel[N],L[M];
int sum[M][N],f[M][M];

int cnt[N];
inline void pre()//預處理
{
    int t=0;
    for(int i=1;i<=bel[n];i++)
        for(int j=1;j<=m;j++) sum[i][j]+=sum[i-1][j];
    for(int i=1;i<=bel[n];i++)
    {
        t=0;
        for(int j=L[i];j<=n;j++)
        {
            cnt[a[j]]++;
            if(!(cnt[a[j]]&1)) t++;
            else if(cnt[a[j]]>1) t--;
            if(bel[j]!=bel[j+1]) f[i][bel[j]]=t;
        }
        for(int j=L[i];j<=n;j++) cnt[a[j]]--;
    }
}
inline void query(int l,int r)//處理詢問
{
    ans=0; int t=0;
    if(bel[l]+1>=bel[r])//對沒有大區間的情況特殊處理
    {
        for(int i=l;i<=r;i++)//直接爆力計算每個數的貢獻
        {
            cnt[a[i]]++;
            if(!(cnt[a[i]]&1)) ans++;
            else if(cnt[a[i]]>1) ans--;
        }
        for(int i=l;i<=r;i++) cnt[a[i]]--;//清空cnt
        return;
    }
    int bl=bel[l-1]+1,br=bel[r+1]-1;//計算bl,br,細節
    //l-1是考慮當l在一個塊最左邊的時候,那l在的塊整塊都會每計算,直接整個拿出來計算就好了,r+1同理
    ans=f[bl][br];//初值
    for(int i=l;i<L[bl];i++)//對左邊的小段計算貢獻
    {
        cnt[a[i]]++; t=cnt[a[i]]+sum[br][a[i]]-sum[bl-1][a[i]];
        if(!(t&1)) ans++;
        else if(t>1) ans--;
    }
    for(int i=L[br+1];i<=r;i++)//對右邊的小段計算貢獻
    {
        cnt[a[i]]++; t=cnt[a[i]]+sum[br][a[i]]-sum[bl-1][a[i]];
        if(!(t&1)) ans++;
        else if(t>1) ans--;
    }
    for(int i=l;i<L[bl];i++) cnt[a[i]]--; for(int i=L[br+1];i<=r;i++) cnt[a[i]]--;//清空
    return;
}
int main()
{
    n=read(); m=read(); q=read();
    int t=sqrt(n)+1;
    for(int i=1;i<=n;i++)
    {
        a[i]=read(); bel[i]=(i-1)/t+1;//處理bel
        if(bel[i]!=bel[i-1]) L[bel[i]]=i;//處理L
        sum[bel[i]][a[i]]++;//此時sum只包括第i塊的數量
    }
    bel[n+1]=bel[n]+1; L[bel[n+1]]=n+1;//重要的細節,有時我的代碼考慮邊界時會訪問到n+1的點

    pre();

    int l,r;
    for(int i=1;i<=q;i++)
    {
        l=read(); r=read();
        l=(l+ans)%n+1; r=(r+ans)%n+1;
        if(l>r) swap(l,r);//處理l,r
        query(l,r);
        printf("%d\n",ans);
    }
    return 0;
}

P4135 作詩