题目大意
就像人类喜欢跳格子游戏一样,FJ的奶牛们发明了一种新的跳格子游戏。虽然这种接近一吨的笨拙的动物玩跳格子游戏几乎总是不愉快地结束,但是这并没有阻止奶牛们在每天下午参加跳格子游戏
游戏在一个R*C (2 <= R <= 750, 2 <= C <= 750)的网格上进行,每个格子有一个取值在1-k之间的整数标号,奶牛开始在左上角的格子,目的是通过若干次跳跃后到达右下角的格子,当且仅当格子A和格子B满足如下条件时能从格子A跳到格子B:
1.B格子在A格子的严格右方(B的列号严格大于A的列号)
2.B格子在A格子的严格下方(B的行号严格大于A的行号)
3.B格子的标号和A格子的标号不同
请你帮助奶牛计算出从左上角的格子到右下角的格子一共有多少种不同的方案。
题目分析
观察条件1,2 ,可以得出只有一个点左上方的点才可以更新该点答案。考虑DP, 容易得出DP转移方程为 dp[i][j] = ∑x<i,y<i,a[x][y]!=a[i][j] (a数组为标号)。
改写此方程,变为 dp[i][j] = ∑(x<i,y<i)a[x][y] - ∑(x<i,y<i,a[x][y]==a[i][j])a[x][y]。前面的明显可以用前缀和维护,考虑如何维护后面的这个式子。
不难看出我们需要单点修改,区间查询。使用线段树维护 当前行 的 上面几行中 标号与当前点(i,j)相同的点的dp值的和。
显然,用一颗线段树无法支持多种标号,所以我们开k颗线段树来维护。
考虑到空间可能不足,这里使用动态开点线段树。
总复杂度为 O(R*C log C) (常数略大)
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 5 const int MAXN=755; 6 const ll Mod=1e9+7; 7 8 struct Tree_Node{ 9 int ls,rs; 10 ll sum; 11 }t[MAXN*MAXN<<4]; 12 int tot; 13 14 int n,m,kk; 15 int a[MAXN][MAXN]; 16 ll sum[MAXN],f[MAXN][MAXN]; 17 18 inline void Update(int &o,int l,int r,int x,ll v){ 19 if(!o) o=++tot; 20 if(l==r){ 21 t[o].sum=(t[o].sum+v)%Mod; 22 return; 23 } 24 int mid=(l+r)>>1; 25 if(x<=mid) Update(t[o].ls,l,mid,x,v); 26 else Update(t[o].rs,mid+1,r,x,v); 27 t[o].sum=(t[t[o].ls].sum+t[t[o].rs].sum)%Mod; 28 } 29 inline ll Query(int k,int l,int r,int x,int y){ 30 if(!k) return 0; 31 if(x<=l&&r<=y) return t[k].sum; 32 int mid=(l+r)>>1; 33 ll res=0; 34 if(x<=mid) res+=Query(t[k].ls,l,mid,x,y); 35 if(y>mid) res+=Query(t[k].rs,mid+1,r,x,y); 36 return res%Mod; 37 } 38 int main(){ 39 scanf("%d%d%d",&n,&m,&kk); 40 tot=kk; 41 for(int i=1;i<=n;++i) 42 for(int j=1;j<=m;++j) 43 scanf("%d",&a[i][j]); 44 for(int i=1;i<m;++i) 45 sum[i]=1; 46 f[1][1]=1; 47 Update(a[1][1],1,m,1,f[1][1]); 48 for(int i=2;i<=n;++i){ 49 for(int j=2;j<=m;++j){ 50 f[i][j]=(sum[j-1]-Query(a[i][j],1,m,1,j-1)+Mod)%Mod; 51 } 52 ll tmp=0; 53 for(int j=2;j<=m;++j){ 54 tmp=(tmp+f[i][j])%Mod; 55 sum[j]=(sum[j]+tmp)%Mod; 56 Update(a[i][j],1,m,j,f[i][j]); 57 } 58 } 59 printf("%lld ",f[n][m]); 60 return 0; 61 }