[bzoj3939_Usaco2015 Feb]Cow Hopscotch(线段树维护DP)

               \;\;\;\;\;\;\; 今天晚上,想到自己的线段树专题中的题还未码完。
               \;\;\;\;\;\;\; 此时,我看了看手边安详的作业,痛下决心,晚上熬夜补吧(唉~ 竞赛生的艰难啊 ~)。
               \;\;\;\;\;\;\; 于是,便有了我今日的博客。
洛谷题面 [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=1x1j=1y1i,ja[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=1x1j=1y1fi,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=1x1j=1y1fi,ja[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=1x1j=1y1fi,ja[i][j]=a[x][y]就可以在a[x][y]所代表的线段树中查找区间(1,y-1)的值(因为我们是按行开始从上往下进行处理的,所以x行以下的数就不会存进去)。
思路便是如此。
时间复杂度为:O(n
m*log nm)
代码实现如下:

#include
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;
}

你可能感兴趣的:(线段树,数据结构)