奇襲:分治,桶


大致的簡化題意在我的考試反思里有,裸n2及以上的暴力也不打算再講了。

 1 #include<cstdio>
 2 #include<ctime>
 3 using namespace std;
 4 #define rus register unsigned short
 5 inline unsigned short max(const rus a,const rus b){return a>b?a:b;}
 6 inline unsigned short min(const rus a,const rus b){return a<b?a:b;}
 7 inline unsigned short read(){
 8     rus a=0;register char ch=getchar();
 9     while(ch<48||ch>57)ch=getchar();
10     while(ch>=48&&ch<=57)a=(a<<3)+(a<<1)+ch-48,ch=getchar();
11     return a;
12 }
13 int ans;
14 unsigned short pos[50005],n;
15 int main(){
16     n=read();
17     for(rus i=1,x,y;i<=n;++i) x=read(),y=read(), pos[x]=y;
18     for(rus i=1,maxx=0,minn=50006;i<=n;++i,maxx=0,minn=50006){
19         if(clock()>990000){printf("%d\n",ans+n-i+1);return 0;}
20         for(rus j=i;j<=n;++j){
21             maxx=max(maxx,pos[j]); minn=min(minn,pos[j]);
22             if(maxx-minn==j-i)ans++;
23         }
24     }
25     
26     printf("%d\n",ans);//printf("%ld\n",clock());
27 }
考場上我的不要臉的n2卡常

跳躍的91分n2還是要講一下的:

看上邊的代碼里的枚舉,我們在循環中不斷更新maxx和minn。

然而我們需要的是maxx-minn==j-i,如果maxx和minn的差值很大,那么j接下來的很多次枚舉都不會更新答案。

我們考慮跳過這段區間直到j=maxx-minn+i(簡單移項別說不會),這才有可能更新答案嘛。

這時j跳了一大段,中間的maxx和minn怎么更新呢?

我們考慮預處理出區間最大最小值,RMQ(ST,線段樹,樹狀數組選你喜歡的就好)

這樣我們就可以跳起來了!枚舉量減小了很多。

但是,答案的累加還是ans++的,而本題最大的答案是50000×50000的正方形里的主對角線。

這時的答案是50000×50001/2>1e9,單純ans++這個思路就會TLE

所以讓我們放棄它,從頭想。

碼農與一個厲害的程序員的區別在哪里?一個只會碼,另一個會思考。

我們對式子動一下手腳吧,假裝我們不是碼農的樣子。

看數據范圍,n log是可以接受的。考慮那些含有二分思想的玩意。

有的題解說線段樹,其實是類似一個動態權值線段樹的玩意,思想大同小異。

一個區間,從中間mid分成兩個區間,如果最小值在左邊最大值在右邊。。。

用maxr表示max(a[mid+1],a[mid+2],...,a[r]),   minl=min(a[mid],a[mid-1],...,a[l])

那么式子是maxr-minl=r-l。稍微移項,maxr-r=minl-l,現在左右兩邊無關了。

對於每一個r,把maxr-r的桶++,對應左邊的minl-l把桶里面的值ans+=桶值。

可能有人不理解桶是什么東西吧?天天愛跑步做了嗎?

沒做也沒關系。

大致思路就是,如果我們需要單點查詢/修改某一個值出現了多少次,怎么辦?

很簡單那,開一個數組,數組的對應位數++,查詢也直接查數組的那一位就好了呀。

這種存權值(少數時候會存權值區間)的數組就被叫做桶。

然而這乍一下維護桶是錯的,為什么呢?

因為在左邊的l從mid向區間左端L枚舉的過程中,並非所有的r都還能滿足我們預設的“最小值在左邊,最大值在右邊”的條件

但是我們仍然能發現,對於每一個左端點l,它所對應的合法的右區間r總是連續的,遞增的。

l繼續向左移--,對應的右區間的兩個端點稱之為rl和rr(是閉區間[rl,rr]),它們一定不會向左移。

證明:上一步rl~rr是最大的合法決策區間,那么l左移后,maxl可能大了,minl可能小了,也可能都不變。

如果maxl變大了,那么maxr還需要滿足大於maxl的話,可能需要把rl右移,它所貢獻的桶值不再有效。

while(rl<=R&&maxx[l]>maxx[rl])t[maxx[rl]-rl]--,rl++;

同理,如果minl變小了,那么能夠滿足minr>minl的右區間可能會擴大,擴建新桶。

while(rr+1<=R&&minn[l]<minn[rr+1])rr++,t[maxx[rr]-rr];

這樣就可以累加l對應的答案了。但是我們可悲的發現答案還是不對,為什么呢?

因為我們並沒有限制rl和rr的大小關系,而實際上可能會出現rl>rr的情況,導致桶中有負值。

有很多解決辦法:控制rl與rr的大小關系,或累加答案時判斷大小關系,或累加答案是判斷桶的正負。

當所有左區間枚舉完畢后,記得清空你的maxx和minn以及t(桶)數組。

不要memset啊同志們,nlog次O(n)的memset想什么呢!

可以改一下memset的參數讓它清區間,或手動for清,都可以。

這樣我們已經考慮了最小值在左最大值在右的情況,接下來說最值都在左側的情況。

再翻回來找出我們的算式:max-min=r-l,移項l+max-min=r

那么就很簡單了,對於每個l,設rrr=l+maxl-minl,如果rrr滿足設定的條件就好了

if(rrr>=mid+1&&rrr<=R&&maxx[rrr]<maxx[l]&&minn[rrr]>minn[l]) ans++;

現在我們已經處理了極值都在左邊的情況和左小右大的情況。

還剩都在右邊和左大右小。這兩種情況嘛。。。我們嘗試把數組翻過來,這兩種情況就變成了上面的那兩種情況。

reverse。但注意要更改一下mid值。如原區間[1,5],reverse前是[1,3]和[4,5]

翻一下之后應該是[5,4]和[3,1],也就是左邊應該有兩個元素,mid=2而不在是原來的3。

沒了,遞歸二分,到l==r直接ans++,return。沒了。

順便提一嘴,桶里那個minl-l之類的可能負過去,你可以學習下tdcp和mikufun的數組負下標操作。

否則記得給它加上50000。

 1 #include<cstdio>
 2 #include<iostream>
 3 #include<algorithm>
 4 #include<cstring>
 5 using namespace std;
 6 int ans,n,a[50005],maxx[50005],minn[50005],t[200005];
 7 void ask(const int l,const int r){
 8     if(l==r){ans++;/*printf("!!!---%d %d---!!!\n",l,ans);*/return;}
 9     register int mid=l+r>>1;
10     ask(l,mid);ask(mid+1,r);//printf("-----%d %d-----\n",l,r);
11     for(int p=0;p<=1;++p){//for(int i=1;i<=n;++i)printf("%d ",a[i]);puts("");
12         for(int i=mid;i>=l;--i) minn[i]=min(minn[i+1],a[i]),maxx[i]=max(maxx[i+1],a[i]);
13         minn[mid+1]=maxx[mid+1]=a[mid+1];
14         for(int i=mid+2;i<=r;++i) minn[i]=min(minn[i-1],a[i]),maxx[i]=max(maxx[i-1],a[i]);
15         for(int i=mid,rl=mid+1,rr=mid;i>=l;--i){//printf("---%d---\n",i);
16             while(rl<=r&&maxx[rl]<maxx[i]) t[maxx[rl]-rl+100000]--,rl++;
17             while(rr+1<=r&&minn[rr+1]>minn[i]) rr++,t[maxx[rr]-rr+100000]++;
18             if(rl<=rr&&t[minn[i]-i+100000]) ans+=t[minn[i]-i+100000];//;printf("%d %d\n",rl,rr);for(int ii=100000-3;ii<=100000+3;++ii)printf("%d ",t[ii]);puts("");
19             if(i==l) while(rl<=rr)t[maxx[rl]-rl+100000]--,rl++;
20             if(i==l) while(rr<rl-1) rr++,t[maxx[rr]-rr+100000]++;
21             //for(int ii=100000-3;ii<=100000+3;++ii)printf("%d ",t[ii]);puts("");
22         }//printf("ans=%d\n",ans);
23         for(int i=mid,rrr=i+maxx[i]-minn[i];i>=l;--i,rrr=i+maxx[i]-minn[i]) 
24             if(rrr<=r&&rrr>=mid+1&&maxx[rrr]<maxx[i]&&minn[rrr]>minn[i]) ans++;
25         for(int i=r;i>=l;--i)minn[i]=0x3fffffff,maxx[i]=0;
26         reverse(a+l,a+r+1);mid=r-mid+l-1;//printf("ans=%d\n",ans);
27     }
28 }
29 int main(){memset(minn,0x3f,sizeof(minn));
30     scanf("%d",&n);for(int i=1,x,y;i<=n;++i)scanf("%d%d",&x,&y),a[x]=y;
31     ask(1,n);
32     printf("%d\n",ans);
33 }
預警:32行


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM