1. 程式人生 > >虔誠的墓主人(BZOJ1227)(洛谷P2154)解題報告

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

emp 裝飾 楊輝三角 span 記錄 upd 藍色 標簽 for

題目描述

小W是一片新造公墓的管理人。公墓可以看成一塊N×M的矩形,矩形的每個格點,要麽種著一棵常青樹,要麽是一塊還沒有歸屬的墓地。

當地的居民都是非常虔誠的基督徒,他們願意提前為自己找一塊合適墓地。為了體現自己對主的真誠,他們希望自己的墓地擁有著較高的虔誠度。

一塊墓地的虔誠度是指以這塊墓地為中心的十字架的數目。一個十字架可以看成中間是墓地,墓地的正上、正下、正左、正右都有恰好k棵常青樹。

小W希望知道他所管理的這片公墓中所有墓地的虔誠度總和是多少。

輸入輸出格式

輸入格式:

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

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

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

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

輸出格式:

輸出文件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;
}

 

 



 

 


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