虔誠的墓主人(BZOJ1227)(洛谷P2154)解題報告

題目描述

小W是一片新造公墓的管理人。公墓能夠當作一塊N×M的矩形,矩形的每一個格點,要麼種着一棵常青樹,要麼是一塊尚未歸屬的墓地。node

當地的居民都是很是虔誠的基督徒,他們願意提早爲本身找一塊合適墓地。爲了體現本身對主的真誠,他們但願本身的墓地擁有着較高的虔誠度。數組

一塊墓地的虔誠度是指以這塊墓地爲中心的十字架的數目。一個十字架能夠當作中間是墓地,墓地的正上、正下、正左、正右都有剛好k棵常青樹。數據結構

小W但願知道他所管理的這片公墓中全部墓地的虔誠度總和是多少。優化

輸入輸出格式

輸入格式:spa

輸入文件religious.in的第一行包含兩個用空格分隔的正整數N和M,表示公墓的寬和長,所以這個矩形公墓共有(N+1) ×(M+1)個格點,左下角的座標爲(0, 0),右上角的座標爲(N, M)。3d

第二行包含一個正整數W,表示公墓中常青樹的個數。code

第三行起共W行,每行包含兩個用空格分隔的非負整數xi和yi,表示一棵常青樹的座標。輸入保證沒有兩棵常青樹擁有相同的座標。orm

最後一行包含一個正整數k,意義如題目所示。blog

輸出格式:排序

輸出文件religious.out僅包含一個非負整數,表示這片公墓中全部墓地的虔誠度總和。爲了方便起見,答案對2,147,483,648取模。

輸入輸出樣例

輸入樣例#1:
5 6
13
0 2
0 3
1 2
1 3
2 0
2 1
2 4
2 5
2 6
3 2
3 3
4 3
5 2
2
輸出樣例#1:
6
題目描述摘自洛谷。



很是幸運過了這道生涯題,從上午10:30到晚上20:15。事實證實,人應該對本身有點信心,應該有點夢想,萬一就見鬼了呢。
題目大意,給出一個N*M的方格,在每一個點上分佈着常青樹/墓地,咱們須要統計每一個墓地的虔誠度,相加求得答案。
題中描述的是「剛好」有k顆樹,看一眼樣例,發現事情沒那麼簡單,再結合答案,很容易分析出咱們應該求的是每一個點上下左右對應的組合數相乘。
由乘法原理可知,咱們要求的是從上下左右的常青樹中分別選出k顆,對應的方案數乘乘積。
(L,R,U,D分別表明Left,Right,Up,Down)
觀察數據範圍,k <= 10,由楊輝三角行列數對應組合數,咱們能夠經過楊輝三角遞推式,預處理出全部的組合數。
然而再看一眼數據範圍,N,M的範圍過大,不用說線性遞推,數組都開不下。然而常青樹W的個數卻只有100,000個,考慮離散化。
在離散化的過程當中,順手處理出每行每列的常青樹個數。(後面在解釋)。
至此,預處理的全部工做已經完成,放個代碼。(碼風醜,請見諒)
離散化:(1倍存原數,2倍存x,3倍存y)
inline void discretizition() { scanf("%d%d%d",&n,&m,&w); for(int i = 1;i<=w;i++) { int x,y; scanf("%d%d",&x,&y); nd[i+w].temp = x; nd[i+w].kind = 1; nd[i+w].num = i; nd[i+2*w].temp = y; nd[i+2*w].kind = 2; nd[i+2*w].num = i; } int cnt = 1; sort(nd+w+1,nd+3*w+1,cmp1); for(int i = w+1;i<=3*w;i++) { if(i==w+1) { int num = nd[i].num; if(nd[i].kind==1) { nd[num].x = cnt; lie[cnt]++; //列數 }else { nd[num].y = cnt; hang[cnt]++; //行數 } continue; } if(nd[i].temp!=nd[i-1].temp)cnt++; int num = nd[i].num; if(nd[i].kind==1) { nd[num].x = cnt; lie[cnt]++; //列數 }else { nd[num].y = cnt; hang[cnt]++; //行數 } } } 
遞推楊輝三角,預處理組合數:
inline void getC() { scanf("%d",&k); C[0][0] = 1; for(int i = 1;i<=W;i++) { for(int j = 0;j<=10;j++) { if(j==0){C[i][j] = 1;continue;} C[i][j] = (C[i-1][j-1]+C[i-1][j])&mod; } } }
 
  
明確一點小技巧,取模2147483648和按位與2147483647效果是同樣的。(1<<31==2147483648)
若是還不明白,請實踐幾組數,或者仔細想一想。畢竟腦殼是用來想的,不是用來裝飾的。
當咱們完成預處理步驟時,咱們已經將整個方格縮小到了100,000×100,000的大小了,接下來即是如何計算虔誠度。
暴力枚舉墓地?必然超時(可是洛谷數據貌似能夠經過一些玄學優化方法過掉,這裏暫不說起)。咱們須要找到一種更爲快速的方法。
When you have eliminated the impossibles,whatever remains,however improbable,must be the truth.——福爾摩斯
咱們但願能出現O(wlog)的方法,或者O(w)的方法。因爲常青樹一共只有100,000顆,既然咱們不能枚舉墓地,不妨枚舉常青樹。
而枚舉常青樹又不能隨意枚舉,否則在累積虔誠度的時候又會變成w方的狀況,咱們須要一些數據結構來維護虔誠度,如何維護?維護什麼?這就是本題的關鍵。
通過咱們的一番分析(標籤)咱們知道本題使用的數據結構是樹狀數組。
不要問我怎麼分析出來的樹狀數組,我就是看到它是樹狀數組才決定的作這道題。
咱們在枚舉的過程當中,想要求的是每一個空地上下左右組合數的乘積,能夠發如今同一行(列)(如下統一爲行)兩顆常青樹之間的空地,它們左面和右面常青樹的組合數是相同的。
這也就給了咱們靈感,咱們能夠經過有順序的枚舉,肯定一維,轉移一維。這時候須要把全部點按照行優先,列其次的順序排序。這樣枚舉的時候就是有序的了。
再瞅一眼圖,找找感受。

假定如今咱們枚舉到了藍色點,咱們在枚舉的時候能夠經過先後點是否在同一行,定義變量來記錄這一行已經枚舉了多少顆常青樹,再經過這一行常青樹的總數,
減去左方的常青樹,便可獲得右方的常青樹,左右的組合數乘積便可輕鬆獲得。這也就是以前要維護每一行常青樹個數的緣由。
那麼如何快速計算同一行兩顆常青樹之間墓地的虔誠度之和?
上面的圖沒有同一行連續的墓地,咱們把它轉90°(awa)。
好,如今在下數起第三行,出現了兩個藍色點,它們左右的常青樹數量相同。
不妨設L爲兩個藍色點左邊組合數,R爲兩個藍色點右邊組合數,U1爲1號藍色點上方組合數,D1爲其下方組合數,U2同理爲2號藍色點上方組合數,D2爲藍色點下方組合數。
咱們要求的這兩個點的虔誠度爲:
Ans = L×R×U1×D1 + L×R×U2×D2
= L×R×(U1×D1 + U2×D2)
看到這裏,您應該已經猜到如何快速計算墓地虔誠度的和了。
樹狀數組裏面存的不是常青樹個數,咱們將每一列的編號數做爲樹狀數組的下標,將這一列在本時刻所對應的上下組合數的乘積存入樹狀數組中,再經過前綴和相減,
算出兩顆常青樹之間的墓地的上下組合數乘積之和。
對應上方例子,即樹狀數組四號點存儲的是U1×D1,五號點存儲的是U2×D2,所求虔誠度:
Ans = L×R×(getsum(5)-getsum(4-1));
在算完兩棵樹之間的墓地後,須要更新對應列的樹狀數組中的值,更新的值即爲從新算出的組合數與以前的組合數之差,對應例子中,4號點應該更新的值即爲:
Update(4,(U1-1)×(D1+1)-U1×D1);
5號點同理。
這裏的D1/U1能夠經過枚舉順序記錄,做者經過自下而上枚舉,因此經過數組記錄了D1,以前又記錄了每一列的常青樹個數,相減便可獲得U1。
逐漸枚舉,更新答案便可。
換行或兩顆常青樹相鄰的狀況讀者自行思考便可,除此之外最好將行列數座標+1,因原題中原點爲(0,0),樹狀數組更新不便。
Talk is cheap,show me the code。
#include<cstdio> #include<algorithm>
#define W 100005
#define mod 2147483647
#define ll long long
using namespace std; int n,m,w,lie[2*W+1],hang[2*W+1],k,tree[2*W],down[2*W]; ll C[2*W+1][12]; ll ans; struct node { int x,y,num,kind,temp; }nd[W*3+2]; inline bool cmp1(node a,node b) { return a.temp<b.temp; } inline bool cmp(node a,node b) { if(a.y!=b.y) { return a.y<b.y; }else { return a.x<b.x; } } inline void discretizition() { scanf("%d%d%d",&n,&m,&w); for(int i = 1;i<=w;i++) { int x,y; scanf("%d%d",&x,&y); nd[i+w].temp = x; nd[i+w].kind = 1; nd[i+w].num = i; nd[i+2*w].temp = y; nd[i+2*w].kind = 2; nd[i+2*w].num = i; } int cnt = 1; sort(nd+w+1,nd+3*w+1,cmp1); for(int i = w+1;i<=3*w;i++) { if(i==w+1) { int num = nd[i].num; if(nd[i].kind==1) { nd[num].x = cnt; lie[cnt]++; }else { nd[num].y = cnt; hang[cnt]++; } continue; } if(nd[i].temp!=nd[i-1].temp)cnt++; int num = nd[i].num; if(nd[i].kind==1) { nd[num].x = cnt; lie[cnt]++; }else { nd[num].y = cnt; hang[cnt]++; } } } inline void getC() { scanf("%d",&k); C[0][0] = 1; for(int i = 1;i<=W;i++) { for(int j = 0;j<=10;j++) { if(j==0) { C[i][j] = 1; continue; } C[i][j] = (C[i-1][j-1]+C[i-1][j])&mod; } } } inline int lowbit(int x) { return x&(-x); } inline void update(int pos,int x) { for(int i = pos;i<=w;i+=lowbit(i)) { tree[i]+=x; } } inline ll getsum(int pos) { ll tmp = 0; for(int i = pos;i;i-=lowbit(i)) { tmp=(tmp+tree[i])&mod; } return tmp&mod; } inline void solve() { sort(nd+1,nd+1+w,cmp); int tot = 0; for(int i = 1;i<=w;i++) { if(i==1) { tot++; down[nd[i].x]++; update(nd[i].x,(C[down[nd[i].x]][k]*C[lie[nd[i].x]-down[nd[i].x]][k])&mod); continue; } if(nd[i].y==nd[i-1].y) { tot++; if((tot-1>=k)&&(hang[nd[i].y]-tot+1>=k)) { ll tp = getsum(nd[i].x-1)-getsum(nd[i-1].x); tp = tp*C[hang[nd[i].y]-tot+1][k]*C[tot-1][k]&mod; ans = (ans+tp)&mod; } down[nd[i].x]++; update(nd[i].x,(C[down[nd[i].x]][k]*C[lie[nd[i].x]-down[nd[i].x]][k]&mod)-(C[down[nd[i].x]-1][k]*C[lie[nd[i].x]-down[nd[i].x]+1][k]&mod)); }else { down[nd[i].x]++; update(nd[i].x,(C[down[nd[i].x]][k]*C[lie[nd[i].x]-down[nd[i].x]][k]&mod)-(C[down[nd[i].x]-1][k]*C[lie[nd[i].x]-down[nd[i].x]+1][k]&mod)); tot = 1; } } printf("%lld\n",ans&mod); } int main() { discretizition(); getC(); solve(); return 0; }
相關文章
相關標籤/搜索