//轉自: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; }