大致的簡化題意在我的考試反思里有,裸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 }
跳躍的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 }