#include<stdio.h> #include<string.h> #include<ctype.h> #include<math.h> #include<iostream> #include<string> #include<set> #include<map> #include<vector> #include<queue> #include<bitset> #include<algorithm> #include<time.h> using namespace std; #define MS(x,y) memset(x,y,sizeof(x)) #define MC(x,y) memcpy(x,y,sizeof(x)) #define MP(x,y) make_pair(x,y) #define ls o<<1 #define rs o<<1|1 typedef long long LL; typedef unsigned long long UL; typedef unsigned int UI; template <class T1,class T2>inline void gmax(T1 &a,T2 b){if(b>a)a=b;} template <class T1,class T2>inline void gmin(T1 &a,T2 b){if(b<a)a=b;} const int N=750+5,M=5e5+10,Z=1e9+7,ms63=1061109567; int casenum,casei; /* 算法介绍之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分治就做完啦 */ int n,m; int a[N][N]; int f[N][N]; int s[M]; void add(int &x,int y) { x+=y; if(x>=Z)x-=Z; } void solve(int l,int r) { if(l==r)return; int mid=(l+r)>>1; //步骤1,处理[l,mid] solve(l,mid); //步骤2,处理[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]]+Z)%Z); for(int i=l;i<=mid;i++) { add(s[a[i][j]],f[i][j]); add(all,f[i][j]); } } //步骤3,处理(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]); MS(f,0);f[1][1]=1; solve(1,n); printf("%d\n",f[n][m]); } return 0; } /* 【题意】 给你一个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)的时间复杂度内解决了。 */