P4135 作詩
阿新 • • 發佈:2018-10-14
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 作詩