cdq分治(陳丹琦分治)


//轉自:http://blog.csdn.net/snowy_smile/article/details/49668689
/*
算法介紹之cdq分治:

其實cdq分治的思想與應用都能被很簡單地描述——它是用來解決各種區間段轉移問題[x->y(x<y)]的。
我們用f[x]表示位置x轉移之后的結果,用solve(l,r)來傳遞完全限制在[l,r]范圍內的狀態轉移,並且轉移a->b一定有a<b
那么對於solve(l,r)
1,如果l==r,沒有后續轉移,程序結束。
2,求出m=(l+r)>>1;
3,solve(l,m);
4,傳遞[l,m]對[m+1,r]的貢獻。
5,solve(m+1,r);
這樣cdq分治就做完啦
*/

/*
【題意】
給你一個n(750)*m(750)的棋盤,每個格子(i,j)都有一種顏色c[i][j],
顏色范圍是[1,top]中的整數,top取值范圍是[1,5e5]。
對於一次跳躍A(y1,x1)->B(y2,x2),我們說這次跳躍是合法的,要求必須有:
1,y2>y1
2,x2>x1
3,c[y1][x1]!=c[y2][x2];
我們想要求出從(1,1)經過若干次跳躍之后到達(n,m)的方案數,mod(1e9+7)

【類型】
CDQ分治

【分析】
首先,我們可以構想一個暴力DP:
用f[i][j]表示從(1,1)到(i,j)的方案數
那么f[i][j]=∑f[u][v],u<i&&v<j&&c[u][v]!=c[i][j]
時間復雜度為O(n^2 m^2),想要通過這題,這還差得遠。

思考1,
我們觀察到,這道題中,顏色的范圍不是1e9,而是5e5。為什么呢?
我們發現,相比較1e9,5e5這個范圍的數組是開得下的,於是我們可以做計數處理,有助於我們維護答案。
(ps:如果顏色屬於1e9范圍,因為顏色種類最多只有n*m,所以其實我們可以離散化)
如果我們用d[x]表示顏色為從(1,1)走到當前處理范圍顏色為x的點的方案數,
並用一個大的變量tot統計從(1,1)走到當前處理范圍所有點的方案數之和。
那么當我們走到一個顏色為x的點時,就可以直接使用 tot-d[x] 來更新到達這個點的方案數。
這個思想有一定的價值,但是因為題目給出的1,2兩個限制條件。
具體要通過什么途徑保證d[]中處理的所有點都在它之間呢?

思考2,
區間問題的實現,很多時候我們都要引入整段考慮的思想。
這其實也就是CDQ分治的思想與具體實現。
我們想要更新從(1,1)走到行[l,r]范圍格點的方案數。
而line[l,r]每個格子更新的來源,有兩方面。
1,line[1,l-1]
2,line[l,r]內部
我們假設在處理[l,r]時,所有line[1,l-1]對line[l,r]的貢獻已經生效。
於是現在只需要考慮來自於line[l,r]內部的貢獻。
並且定義過程solve(l,r),用於解決[l,r]內部貢獻的轉移。

具體如何實現?
回歸到這道題——

1,如果l==r,那不會生成任何貢獻,直接結束。
2,否則我們設m=(l+r)>>1;
接下來只要依次實現以下3個步驟,這道題就做完了。
(1),solve(l,m);
(2),從[l,m]向[m+1,r]轉移貢獻;
(3),solve(m+1,r);
於是,關鍵落在要如何實現(2)上——
此時,我們發現行關系已經是嚴格的小於關系,只需要使得列關系滿足要求。如何使得?
因為問題不是在數軸上,而是在矩形中。
所以目前處理的區間是一個塊,行數是[l,r],每行有m列。
我們現在是想要把[l,mid]的狀態傳遞到[mid+1,r]。
發現只需要從左向右一列列枚舉。
狀態結果的傳遞在(mid,r])實現,狀態來源的累計在[l,mid]實現。
就保證了行與列都是嚴格小於關系,這道題也就在O(nmlogn)的時間復雜度內解決了。

*/
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

typedef long long ll;
const int inf = 0x3f3f3f3f;
const int mod = 1000000007;
const int maxn = 800;
int n,m,a[maxn][maxn],f[maxn][maxn],s[100000];
void add(int &x,int y){
    x+=y;
    x%=mod;
}
void solve(int l,int r){
    if(l==r) return;
    int mid=(l+r)>>1;
    //第一步:處理[l,mid]
    solve(l,mid);
    //第二步:處理[l.mid]->(mid,r]的轉移
    for(int i=l;i<=r;i++){
        for(int j=1;j<=m;j++){
            s[a[i][j]]=0;
        }
    }
    int all=0;
    //從左到右逐列枚舉,以及拓撲逆序,保證了列的小於關系
    for(int j=1;j<=m;j++){
        //行累加只在[l,mid]展開,行轉移在(mid,r]收集,保證了行的小於關系
        for(int i=mid+1;i<=r;i++) add(f[i][j],(all-s[a[i][j]]+mod)%mod);
        for(int i=l;i<=mid;i++){
            add(s[a[i][j]],f[i][j]);
            add(all,f[i][j]);
        }
    }
    //第三步:處理(mid,r]
    solve(mid+1,r);
}
int main(){
    while(~scanf("%d%d%*d",&n,&m)){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                scanf("%d",&a[i][j]);
            }
        }
        memset(f,0,sizeof(f));
        f[1][1]=1;
        solve(1,n);
        printf("%d\n",f[n][m]);
    }
    return 0;
}

 


免責聲明!

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



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