\;\;\;\;\;\;\;
今天晚上,想到自己的线段树专题中的题还未码完。
\;\;\;\;\;\;\;
此时,我看了看手边安详的作业,痛下决心,晚上熬夜补吧(唉~ 竞赛生的艰难啊 ~)。
\;\;\;\;\;\;\;
于是,便有了我今日的博客。
洛谷题面 [bzoj3939_Usaco2015 Feb]Cow Hopscotch
以上链接便是相应的题目;
拿到这道题,我们可以分析一下题目性质。
对于
点
(
x
,
y
)
点(x,y)
点(x,y)来说,能跳到它上面的点的集合为:
∑
i
=
1
x
−
1
∑
j
=
1
y
−
1
(
i
,
j
)
(
a
[
i
]
[
j
]
!
=
a
[
x
]
[
y
]
,
a
数
组
代
表
编
号
)
\sum\limits^{x-1}_{i=1}\sum\limits^{y-1}_{j=1}(i,j)(a[i][j]!=a[x][y],a数组代表编号)
i=1∑x−1j=1∑y−1(i,j)(a[i][j]!=a[x][y],a数组代表编号)。
遇到这种在二维数组内求解方案数的题目,我们会很自然而然地想到区间DP,更何况n,m的数值还比较小。最暴力的方法,便是四重循环,在外面两层枚举行与列,在内两层枚举1~ x-1, 1 ~ y-1。然后,将符合条件的f[i][j]赋值到f[x][y]上。
但是,
n
4
n^4
n4的时间复杂度,肯定会爆。(但据说数据太水了,
n
4
n^4
n4的算法都可以过。)
那么,我们就需要线段树这种数据结构来维护了。
根据二维前缀和思想,我们可以再开一个数组sum,记录f(也就是DP数组)的前缀和。
那么,对于当前的f[x][y],我们可以用sum[x-1][y-1]减去
∑
i
=
1
x
−
1
∑
j
=
1
y
−
1
f
(
i
,
j
)
\sum\limits^{x-1}_{i=1}\sum\limits^{y-1}_{j=1}f(i,j)
i=1∑x−1j=1∑y−1f(i,j),且a[i][j]=a[x][y]。
前缀和的问题我们不用考虑了,剩下的就是怎么快速求
∑
i
=
1
x
−
1
∑
j
=
1
y
−
1
f
(
i
,
j
)
(
a
[
i
]
[
j
]
=
a
[
x
]
[
y
]
)
\sum\limits^{x-1}_{i=1}\sum\limits^{y-1}_{j=1}f(i,j)(a[i][j]=a[x][y])
i=1∑x−1j=1∑y−1f(i,j)(a[i][j]=a[x][y])。
思考一下,每个点的编号最大是nm,且n,m都是比较小的常数。那么,我们可以对每个编号开一个线段树,用来记录从上到下,编号为i的点的f值。
那么,对于当前的点(x,y),
∑
i
=
1
x
−
1
∑
j
=
1
y
−
1
f
(
i
,
j
)
(
a
[
i
]
[
j
]
=
a
[
x
]
[
y
]
)
\sum\limits^{x-1}_{i=1}\sum\limits^{y-1}_{j=1}f(i,j)(a[i][j]=a[x][y])
i=1∑x−1j=1∑y−1f(i,j)(a[i][j]=a[x][y])就可以在a[x][y]所代表的线段树中查找区间(1,y-1)的值(因为我们是按行开始从上往下进行处理的,所以x行以下的数就不会存进去)。
思路便是如此。
时间复杂度为:O(nm*log nm)
代码实现如下:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mo=1e9+7;
const int M=1e6+10;
struct ao
{
int ls,rs;
int num;
#define ls(x) tree[x].ls
#define rs(x) tree[x].rs
#define num(x) tree[x].num
}tree[M*15];
int n,m,k,a[800][800],cnt,root[600000];
ll f[800][800],sum[800][800];
void change(int &p,int l,int r,int wei,int val)
{
if(!p) p=++cnt;
num(p)+=val;
num(p)%=mo;
if(l==r) return;
int mid=l+r>>1;
if(wei<=mid) change(ls(p),l,mid,wei,val);
else change(rs(p),mid+1,r,wei,val);
}
ll cz(int p,int l,int r,int L,int R)
{
if(!p) return 0;
if(l>=L&&r<=R)
{
return num(p)%mo;
}
ll ans=0;
int mid=l+r>>1;
if(L<=mid) ans+=cz(ls(p),l,mid,L,R)%mo;
if(R>mid) ans+=cz(rs(p),mid+1,r,L,R)%mo;
return ans%mo;
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
scanf("%d",&a[i][j]);
}
f[1][1]=sum[1][1]=1;
change(root[a[1][1]],1,m,1,f[1][1]);
for(int i=2;i<=n;i++)
sum[i][1]=1;
for(int i=2;i<=m;i++)
sum[1][i]=1;
for(int i=2;i<=n;i++)
{
for(int j=2;j<=m;j++) f[i][j]=(sum[i-1][j-1]%mo-cz(root[a[i][j]],1,m,1,j-1)%mo+mo)%mo;;
for(int j=2;j<=m;j++) sum[i][j]=(sum[i-1][j]%mo+sum[i][j-1]%mo+f[i][j]-sum[i-1][j-1]%mo+mo)%mo;
for(int j=2;j<=m;j++) change(root[a[i][j]],1,m,j,f[i][j]);
}
cout<<f[n][m];
return 0;
}